├── settings.gradle ├── .gitignore ├── screenshots ├── phone.png ├── android_tv.png ├── icon-web.png ├── android_auto.png ├── android_wear_1.png ├── android_wear_2.png ├── phone_cast_dialog.png ├── phone_lockscreen.png └── phone_fullscreen_player.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── mobile ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── bg.png │ │ │ │ ├── default_background.xml │ │ │ │ ├── fullscreen_toolbar_bg_gradient.xml │ │ │ │ ├── fullscreen_bg_gradient.xml │ │ │ │ └── ic_equalizer_white_36dp.xml │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_menu.png │ │ │ │ ├── ic_star_on.png │ │ │ │ ├── ic_by_genre.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_star_off.png │ │ │ │ ├── ic_default_art.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_launcher_white.png │ │ │ │ ├── ic_close_black_24dp.png │ │ │ │ ├── ic_pause_black_36dp.png │ │ │ │ ├── ic_allmusic_black_24dp.png │ │ │ │ ├── ic_skip_next_white_24dp.png │ │ │ │ ├── ic_skip_next_white_48dp.png │ │ │ │ ├── ic_equalizer1_white_36dp.png │ │ │ │ ├── ic_equalizer2_white_36dp.png │ │ │ │ ├── ic_equalizer3_white_36dp.png │ │ │ │ ├── ic_play_arrow_black_36dp.png │ │ │ │ ├── uamp_ic_pause_white_24dp.png │ │ │ │ ├── uamp_ic_pause_white_48dp.png │ │ │ │ ├── ic_info_outline_grey600_48dp.png │ │ │ │ ├── ic_playlist_music_black_24dp.png │ │ │ │ ├── ic_skip_previous_white_24dp.png │ │ │ │ ├── ic_skip_previous_white_48dp.png │ │ │ │ ├── uamp_ic_play_arrow_white_24dp.png │ │ │ │ └── uamp_ic_play_arrow_white_48dp.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_menu.png │ │ │ │ ├── ic_star_on.png │ │ │ │ ├── ic_by_genre.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_star_off.png │ │ │ │ ├── ic_default_art.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_launcher_white.png │ │ │ │ ├── ic_close_black_24dp.png │ │ │ │ ├── ic_pause_black_36dp.png │ │ │ │ ├── ic_allmusic_black_24dp.png │ │ │ │ ├── ic_skip_next_white_24dp.png │ │ │ │ ├── ic_skip_next_white_48dp.png │ │ │ │ ├── ic_equalizer1_white_36dp.png │ │ │ │ ├── ic_equalizer2_white_36dp.png │ │ │ │ ├── ic_equalizer3_white_36dp.png │ │ │ │ ├── ic_play_arrow_black_36dp.png │ │ │ │ ├── uamp_ic_pause_white_24dp.png │ │ │ │ ├── uamp_ic_pause_white_48dp.png │ │ │ │ ├── ic_info_outline_grey600_48dp.png │ │ │ │ ├── ic_playlist_music_black_24dp.png │ │ │ │ ├── ic_skip_previous_white_24dp.png │ │ │ │ ├── ic_skip_previous_white_48dp.png │ │ │ │ ├── uamp_ic_play_arrow_white_24dp.png │ │ │ │ └── uamp_ic_play_arrow_white_48dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_menu.png │ │ │ │ ├── banner_tv.png │ │ │ │ ├── ic_by_genre.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_star_off.png │ │ │ │ ├── ic_star_on.png │ │ │ │ ├── ic_default_art.png │ │ │ │ ├── ic_launcher_white.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_close_black_24dp.png │ │ │ │ ├── ic_pause_black_36dp.png │ │ │ │ ├── ic_allmusic_black_24dp.png │ │ │ │ ├── ic_equalizer1_white_36dp.png │ │ │ │ ├── ic_equalizer2_white_36dp.png │ │ │ │ ├── ic_equalizer3_white_36dp.png │ │ │ │ ├── ic_play_arrow_black_36dp.png │ │ │ │ ├── ic_skip_next_white_24dp.png │ │ │ │ ├── ic_skip_next_white_48dp.png │ │ │ │ ├── uamp_ic_pause_white_24dp.png │ │ │ │ ├── uamp_ic_pause_white_48dp.png │ │ │ │ ├── ic_skip_previous_white_24dp.png │ │ │ │ ├── ic_skip_previous_white_48dp.png │ │ │ │ ├── ic_info_outline_grey600_48dp.png │ │ │ │ ├── ic_playlist_music_black_24dp.png │ │ │ │ ├── uamp_ic_play_arrow_white_24dp.png │ │ │ │ └── uamp_ic_play_arrow_white_48dp.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_menu.png │ │ │ │ ├── ic_by_genre.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_star_off.png │ │ │ │ ├── ic_star_on.png │ │ │ │ ├── ic_default_art.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_launcher_white.png │ │ │ │ ├── ic_close_black_24dp.png │ │ │ │ ├── ic_pause_black_36dp.png │ │ │ │ ├── ic_allmusic_black_24dp.png │ │ │ │ ├── ic_skip_next_white_24dp.png │ │ │ │ ├── ic_skip_next_white_48dp.png │ │ │ │ ├── ic_equalizer1_white_36dp.png │ │ │ │ ├── ic_equalizer2_white_36dp.png │ │ │ │ ├── ic_equalizer3_white_36dp.png │ │ │ │ ├── ic_play_arrow_black_36dp.png │ │ │ │ ├── uamp_ic_pause_white_24dp.png │ │ │ │ ├── uamp_ic_pause_white_48dp.png │ │ │ │ ├── ic_info_outline_grey600_48dp.png │ │ │ │ ├── ic_playlist_music_black_24dp.png │ │ │ │ ├── ic_skip_previous_white_24dp.png │ │ │ │ ├── ic_skip_previous_white_48dp.png │ │ │ │ ├── uamp_ic_play_arrow_white_24dp.png │ │ │ │ └── uamp_ic_play_arrow_white_48dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_menu.png │ │ │ │ ├── ic_star_on.png │ │ │ │ ├── ic_by_genre.png │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_star_off.png │ │ │ │ ├── ic_default_art.png │ │ │ │ ├── ic_notification.png │ │ │ │ ├── ic_launcher_white.png │ │ │ │ ├── ic_close_black_24dp.png │ │ │ │ ├── ic_pause_black_36dp.png │ │ │ │ ├── ic_allmusic_black_24dp.png │ │ │ │ ├── ic_equalizer1_white_36dp.png │ │ │ │ ├── ic_equalizer2_white_36dp.png │ │ │ │ ├── ic_equalizer3_white_36dp.png │ │ │ │ ├── ic_play_arrow_black_36dp.png │ │ │ │ ├── ic_skip_next_white_24dp.png │ │ │ │ ├── ic_skip_next_white_48dp.png │ │ │ │ ├── uamp_ic_pause_white_24dp.png │ │ │ │ ├── uamp_ic_pause_white_48dp.png │ │ │ │ ├── ic_skip_previous_white_24dp.png │ │ │ │ ├── ic_skip_previous_white_48dp.png │ │ │ │ ├── ic_info_outline_grey600_48dp.png │ │ │ │ ├── ic_playlist_music_black_24dp.png │ │ │ │ ├── uamp_ic_play_arrow_white_24dp.png │ │ │ │ └── uamp_ic_play_arrow_white_48dp.png │ │ │ ├── xml │ │ │ │ └── automotive_app_desc.xml │ │ │ ├── values │ │ │ │ ├── ids.xml │ │ │ │ ├── strings_notifications.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── values-h800dp │ │ │ │ └── dimens.xml │ │ │ ├── animator │ │ │ │ ├── slide_in_from_bottom.xml │ │ │ │ ├── slide_in_from_left.xml │ │ │ │ ├── slide_in_from_right.xml │ │ │ │ ├── slide_out_to_bottom.xml │ │ │ │ ├── slide_out_to_left.xml │ │ │ │ └── slide_out_to_right.xml │ │ │ ├── anim │ │ │ │ ├── fade_in.xml │ │ │ │ └── fade_out.xml │ │ │ ├── animator-v21 │ │ │ │ ├── slide_in_from_bottom.xml │ │ │ │ ├── slide_in_from_left.xml │ │ │ │ ├── slide_in_from_right.xml │ │ │ │ ├── slide_out_to_bottom.xml │ │ │ │ └── slide_out_to_left.xml │ │ │ ├── menu │ │ │ │ ├── main.xml │ │ │ │ └── drawer.xml │ │ │ ├── layout │ │ │ │ ├── tv_vertical_grid.xml │ │ │ │ ├── tv_playback_controls.xml │ │ │ │ ├── tv_activity_player.xml │ │ │ │ ├── nav_header.xml │ │ │ │ ├── include_toolbar.xml │ │ │ │ ├── fragment_list.xml │ │ │ │ ├── activity_player.xml │ │ │ │ ├── media_list_item.xml │ │ │ │ ├── activity_placeholder.xml │ │ │ │ └── fragment_playback_controls.xml │ │ │ └── values-v21 │ │ │ │ └── styles.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── uamp │ │ │ ├── ui │ │ │ ├── MediaBrowserProvider.java │ │ │ ├── PlaceholderActivity.java │ │ │ ├── NowPlayingActivity.java │ │ │ ├── tv │ │ │ │ ├── TvVerticalGridActivity.java │ │ │ │ ├── TvBrowseActivity.java │ │ │ │ └── TvPlaybackActivity.java │ │ │ └── MediaItemViewHolder.java │ │ │ ├── model │ │ │ ├── MusicProviderSource.java │ │ │ ├── MutableMediaMetadata.java │ │ │ └── RemoteJSONSource.java │ │ │ ├── utils │ │ │ ├── NetworkHelper.java │ │ │ ├── TvHelper.java │ │ │ ├── ResourceHelper.java │ │ │ ├── WearHelper.java │ │ │ ├── LogHelper.java │ │ │ ├── BitmapHelper.java │ │ │ ├── CarHelper.java │ │ │ └── MediaIDHelper.java │ │ │ ├── CastOptionsProvider.java │ │ │ ├── playback │ │ │ └── Playback.java │ │ │ ├── VoiceSearchParams.java │ │ │ └── AlbumArtCache.java │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── uamp │ │ │ ├── playback │ │ │ ├── SimplePlaybackServiceCallback.java │ │ │ ├── SimpleMetadataUpdateListener.java │ │ │ └── SimplePlayback.java │ │ │ ├── TestSetupHelper.java │ │ │ └── utils │ │ │ └── SimpleMusicProviderSource.java │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── android │ │ └── uamp │ │ └── utils │ │ └── MediaIDHelperTest.java ├── .gitignore ├── libs │ └── licenses │ │ └── gradle.LICENSE ├── proguard-rules.pro └── build.gradle ├── docs └── cast_styled_receiver │ ├── uamp_cast_logo.png │ ├── uamp_cast_splash.png │ ├── uamp_cast_watermark.png │ └── style.css ├── .google └── packaging.yaml ├── gradle.properties ├── CONTRIB.md ├── README.md ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':mobile' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | *.iml 7 | -------------------------------------------------------------------------------- /screenshots/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/phone.png -------------------------------------------------------------------------------- /screenshots/android_tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/android_tv.png -------------------------------------------------------------------------------- /screenshots/icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/icon-web.png -------------------------------------------------------------------------------- /screenshots/android_auto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/android_auto.png -------------------------------------------------------------------------------- /screenshots/android_wear_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/android_wear_1.png -------------------------------------------------------------------------------- /screenshots/android_wear_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/android_wear_2.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /screenshots/phone_cast_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/phone_cast_dialog.png -------------------------------------------------------------------------------- /screenshots/phone_lockscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/phone_lockscreen.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable/bg.png -------------------------------------------------------------------------------- /screenshots/phone_fullscreen_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/screenshots/phone_fullscreen_player.png -------------------------------------------------------------------------------- /docs/cast_styled_receiver/uamp_cast_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/docs/cast_styled_receiver/uamp_cast_logo.png -------------------------------------------------------------------------------- /docs/cast_styled_receiver/uamp_cast_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/docs/cast_styled_receiver/uamp_cast_splash.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_menu.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_menu.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_menu.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_star_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_star_on.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_star_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_star_on.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/banner_tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/banner_tv.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_menu.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_menu.png -------------------------------------------------------------------------------- /docs/cast_styled_receiver/uamp_cast_watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/docs/cast_styled_receiver/uamp_cast_watermark.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_by_genre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_by_genre.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_star_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_star_off.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_by_genre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_by_genre.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_star_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_star_off.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_by_genre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_by_genre.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_star_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_star_off.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_star_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_star_on.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_by_genre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_by_genre.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_star_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_star_off.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_star_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_star_on.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_star_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_star_on.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_default_art.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_default_art.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_default_art.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_by_genre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_by_genre.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_star_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_star_off.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_launcher_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_launcher_white.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_launcher_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_launcher_white.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_launcher_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_launcher_white.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_default_art.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_default_art.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_close_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_close_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_close_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_close_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_close_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_close_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_launcher_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_launcher_white.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_launcher_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_launcher_white.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_allmusic_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_allmusic_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_skip_next_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_skip_next_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_allmusic_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_allmusic_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_skip_next_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_skip_next_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_skip_next_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_skip_next_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_allmusic_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_allmusic_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_equalizer1_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_equalizer1_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_equalizer2_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_equalizer2_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_equalizer3_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_equalizer3_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/uamp_ic_pause_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/uamp_ic_pause_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/uamp_ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/uamp_ic_pause_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_equalizer1_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_equalizer1_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_equalizer2_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_equalizer2_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_equalizer3_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_equalizer3_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/uamp_ic_pause_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/uamp_ic_pause_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/uamp_ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/uamp_ic_pause_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_equalizer1_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_equalizer1_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_equalizer2_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_equalizer2_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_equalizer3_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_equalizer3_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_skip_next_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_skip_next_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/uamp_ic_pause_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/uamp_ic_pause_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/uamp_ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/uamp_ic_pause_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_allmusic_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_allmusic_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_skip_next_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_skip_next_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_allmusic_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_allmusic_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_info_outline_grey600_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_info_outline_grey600_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_playlist_music_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_playlist_music_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_skip_previous_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/ic_skip_previous_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_info_outline_grey600_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_info_outline_grey600_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_playlist_music_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_playlist_music_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_skip_previous_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_skip_previous_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_skip_previous_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/ic_skip_previous_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_skip_previous_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_skip_previous_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_equalizer1_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_equalizer1_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_equalizer2_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_equalizer2_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_equalizer3_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_equalizer3_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/uamp_ic_pause_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/uamp_ic_pause_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/uamp_ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/uamp_ic_pause_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_equalizer1_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_equalizer1_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_equalizer2_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_equalizer2_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_equalizer3_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_equalizer3_white_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_skip_next_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_skip_next_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/uamp_ic_pause_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/uamp_ic_pause_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/uamp_ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/uamp_ic_pause_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/uamp_ic_play_arrow_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/uamp_ic_play_arrow_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/uamp_ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-hdpi/uamp_ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/uamp_ic_play_arrow_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/uamp_ic_play_arrow_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/uamp_ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-mdpi/uamp_ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_info_outline_grey600_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_info_outline_grey600_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_playlist_music_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/ic_playlist_music_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/uamp_ic_play_arrow_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/uamp_ic_play_arrow_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/uamp_ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xhdpi/uamp_ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_info_outline_grey600_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_info_outline_grey600_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_playlist_music_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_playlist_music_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_skip_previous_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/ic_skip_previous_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/uamp_ic_play_arrow_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/uamp_ic_play_arrow_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/uamp_ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxhdpi/uamp_ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_info_outline_grey600_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_info_outline_grey600_48dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/ic_playlist_music_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/ic_playlist_music_black_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/uamp_ic_play_arrow_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/uamp_ic_play_arrow_white_24dp.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/uamp_ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/android-UniversalMusicPlayer/master/mobile/src/main/res/drawable-xxxhdpi/uamp_ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jun 02 15:18:55 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/default_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | -------------------------------------------------------------------------------- /docs/cast_styled_receiver/style.css: -------------------------------------------------------------------------------- 1 | .background { 2 | background-image: linear-gradient(#000000, #250900); 3 | } 4 | 5 | .logo { 6 | /* max 1280x400, padding-top: 25% */ 7 | background-image: url(uamp_cast_logo.png); 8 | background-size: auto 30%; 9 | } 10 | 11 | .progressBar { 12 | background-color: #FF5A26; 13 | } 14 | 15 | .splash { 16 | /* max 1280x400, padding-top: 25% */ 17 | background-image: url(uamp_cast_splash.png); 18 | } 19 | 20 | .watermark { 21 | background-image: url(uamp_cast_watermark.png); 22 | background-size: auto 57px; 23 | } 24 | -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # files for the dex VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # generated files 13 | bin/ 14 | gen/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Windows thumbnail db 20 | Thumbs.db 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | 29 | # Android Studio 30 | .idea 31 | *.iml 32 | 33 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 34 | .gradle 35 | build/ 36 | -------------------------------------------------------------------------------- /mobile/libs/licenses/gradle.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2007-2011 the original author or authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /mobile/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/google/home/mangini/tools/android-studio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /mobile/src/main/res/xml/automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mobile/src/main/res/values-h800dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 96dp 20 | 21 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/MediaBrowserProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui; 17 | 18 | import android.support.v4.media.MediaBrowserCompat; 19 | 20 | public interface MediaBrowserProvider { 21 | MediaBrowserCompat getMediaBrowser(); 22 | } 23 | -------------------------------------------------------------------------------- /.google/packaging.yaml: -------------------------------------------------------------------------------- 1 | # GOOGLE SAMPLE PACKAGING DATA 2 | # 3 | # This file is used by Google as part of our samples packaging process. 4 | # End users may safely ignore this file. It has no relevance to other systems. 5 | --- 6 | status: PUBLISHED 7 | technologies: [Android, Android Auto, Google Cast, Android Wear, Android TV] 8 | categories: [Getting Started, Media, UI] 9 | languages: [Java] 10 | solutions: [Mobile] 11 | 12 | github: googlesamples/android-UniversalMusicPlayer 13 | 14 | level: INTERMEDIATE 15 | 16 | icon: screenshots/icon-web.png 17 | 18 | apiRefs: 19 | - android:android.media.session.MediaSession 20 | - android:android.media.session.MediaSession.Callback 21 | - android:android.media.session.MediaController 22 | - android:android.service.media.MediaBrowserService 23 | - android:android.media.browse.MediaBrowser 24 | - android:android.app.Notification.MediaStyle 25 | 26 | license: apache2-android 27 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_in_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_out_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/fullscreen_toolbar_bg_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/strings_notifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Stop Casting 19 | Pause 20 | Play 21 | Previous 22 | Next 23 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/fullscreen_bg_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/model/MusicProviderSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.model; 18 | 19 | import android.support.v4.media.MediaMetadataCompat; 20 | 21 | import java.util.Iterator; 22 | 23 | public interface MusicProviderSource { 24 | String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__"; 25 | Iterator iterator(); 26 | } 27 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator-v21/slide_in_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator-v21/slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator-v21/slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator-v21/slide_out_to_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | -------------------------------------------------------------------------------- /mobile/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_equalizer_white_36dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/tv_vertical_grid.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/tv_playback_controls.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator-v21/slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/PlaceholderActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui; 17 | 18 | import android.os.Bundle; 19 | 20 | import com.example.android.uamp.R; 21 | 22 | /** 23 | * Placeholder activity for features that are not implemented in this sample, but 24 | * are in the navigation drawer. 25 | */ 26 | public class PlaceholderActivity extends BaseActivity { 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_placeholder); 32 | initializeToolbar(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/main/res/menu/drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/com/example/android/uamp/playback/SimplePlaybackServiceCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.playback; 17 | 18 | import android.support.v4.media.session.PlaybackStateCompat; 19 | 20 | class SimplePlaybackServiceCallback implements PlaybackManager.PlaybackServiceCallback { 21 | @Override 22 | public void onPlaybackStart() { 23 | 24 | } 25 | 26 | @Override 27 | public void onNotificationRequired() { 28 | 29 | } 30 | 31 | @Override 32 | public void onPlaybackStop() { 33 | 34 | } 35 | 36 | @Override 37 | public void onPlaybackStateUpdated(PlaybackStateCompat newState) { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/tv_activity_player.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/nav_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/com/example/android/uamp/playback/SimpleMetadataUpdateListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.playback; 17 | 18 | import android.support.v4.media.MediaMetadataCompat; 19 | import android.support.v4.media.session.MediaSessionCompat; 20 | 21 | import java.util.List; 22 | 23 | public class SimpleMetadataUpdateListener implements QueueManager.MetadataUpdateListener{ 24 | @Override 25 | public void onMetadataChanged(MediaMetadataCompat metadata) { 26 | } 27 | 28 | @Override 29 | public void onMetadataRetrieveError() { 30 | } 31 | 32 | @Override 33 | public void onCurrentQueueIndexUpdated(int queueIndex) { 34 | } 35 | 36 | @Override 37 | public void onQueueUpdated(String title, List newQueue) { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.utils; 18 | 19 | import android.content.Context; 20 | import android.net.ConnectivityManager; 21 | import android.net.NetworkInfo; 22 | 23 | /** 24 | * Generic reusable network methods. 25 | */ 26 | public class NetworkHelper { 27 | /** 28 | * @param context to use to check for network connectivity. 29 | * @return true if connected, false otherwise. 30 | */ 31 | public static boolean isOnline(Context context) { 32 | ConnectivityManager connMgr = (ConnectivityManager) 33 | context.getSystemService(Context.CONNECTIVITY_SERVICE); 34 | NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); 35 | return (networkInfo != null && networkInfo.isConnected()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/include_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # Project-wide Gradle settings. 17 | 18 | # IDE (e.g. Android Studio) users: 19 | # Gradle settings configured through the IDE *will override* 20 | # any settings specified in this file. 21 | 22 | # For more details on how to configure your build environment visit 23 | # http://www.gradle.org/docs/current/userguide/build_environment.html 24 | 25 | # Specifies the JVM arguments used for the daemon process. 26 | # The setting is particularly useful for tweaking memory settings. 27 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 28 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 29 | 30 | # When configured, Gradle will run in incubating parallel mode. 31 | # This option should only be used with decoupled projects. More details, visit 32 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 33 | # org.gradle.parallel=true 34 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/CastOptionsProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp; 18 | 19 | import android.content.Context; 20 | 21 | import com.google.android.gms.cast.framework.CastOptions; 22 | import com.google.android.gms.cast.framework.OptionsProvider; 23 | import com.google.android.gms.cast.framework.SessionProvider; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Specify receiver application ID for cast 29 | */ 30 | public class CastOptionsProvider implements OptionsProvider { 31 | 32 | @Override 33 | public CastOptions getCastOptions(Context context) { 34 | return new CastOptions.Builder() 35 | .setReceiverApplicationId(context.getString(R.string.cast_application_id)) 36 | .build(); 37 | } 38 | 39 | @Override 40 | public List getAdditionalSessionProviders(Context context) { 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/com/example/android/uamp/TestSetupHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp; 18 | 19 | import com.example.android.uamp.model.MusicProvider; 20 | import com.example.android.uamp.model.MusicProviderSource; 21 | import com.example.android.uamp.utils.SimpleMusicProviderSource; 22 | 23 | import java.util.concurrent.CountDownLatch; 24 | 25 | public class TestSetupHelper { 26 | 27 | public static MusicProvider setupMusicProvider(MusicProviderSource source) 28 | throws Exception { 29 | final CountDownLatch signal = new CountDownLatch(1); 30 | MusicProvider provider = new MusicProvider(source); 31 | provider.retrieveMediaAsync(new MusicProvider.Callback() { 32 | @Override 33 | public void onMusicCatalogReady(boolean success) { 34 | signal.countDown(); 35 | } 36 | }); 37 | signal.await(); 38 | return provider; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/TvHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import android.app.UiModeManager; 19 | import android.content.Context; 20 | import android.content.res.Configuration; 21 | 22 | public class TvHelper { 23 | private static final String TAG = LogHelper.makeLogTag(TvHelper.class); 24 | 25 | /** 26 | * Returns true when running Android TV 27 | * 28 | * @param c Context to detect UI Mode. 29 | * @return true when device is running in tv mode, false otherwise. 30 | */ 31 | public static boolean isTvUiMode(Context c) { 32 | UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE); 33 | if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 34 | LogHelper.d(TAG, "Running in TV mode"); 35 | return true; 36 | } else { 37 | LogHelper.d(TAG, "Running on a non-TV mode"); 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIB.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement (CLA). 9 | 10 | * If you are an individual writing original source code and you're sure you 11 | own the intellectual property, then you'll need to sign an [individual CLA] 12 | (https://developers.google.com/open-source/cla/individual). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA] 15 | (https://developers.google.com/open-source/cla/corporate). 16 | 17 | Follow either of the two links above to access the appropriate CLA and 18 | instructions for how to sign and return it. Once we receive it, we'll be able to 19 | accept your pull requests. 20 | 21 | ## Contributing A Patch 22 | 23 | 1. Submit an issue describing your proposed change to the repo in question. 24 | 1. The repo owner will respond to your issue promptly. 25 | 1. If your proposed change is accepted, and you haven't already done so, sign a 26 | Contributor License Agreement (see details above). 27 | 1. Fork the desired repo, develop and test your code changes. 28 | 1. Ensure that your code adheres to the existing style in the sample to which 29 | you are contributing. Refer to the 30 | [Android Code Style Guide] 31 | (https://source.android.com/source/code-style.html) for the 32 | recommended coding standards for this organization. 33 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 34 | 1. Submit a pull request. 35 | 36 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #ffff5722 19 | #ffbf360c 20 | #00B8D4 21 | #00B8D4 22 | @color/bt_accent 23 | #ccc 24 | @color/bt_accent 25 | #ffff1600 26 | #feee 27 | #ff333333 28 | #E5CCCCCC 29 | #E5FFFFFF 30 | #ff0097A7 31 | #000000 32 | #DDDDDD 33 | #0096a6 34 | -------------------------------------------------------------------------------- /mobile/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4dp 20 | 16dp 21 | 22 | 23 | 8dp 24 | 64dp 25 | 8dp 26 | 27 | 28 | 32dp 29 | 48dp 30 | 16dp 31 | 32 | 33 | 72dp 34 | 12dp 35 | 60dp 36 | 37 | 50dp 38 | 39 | 40 | 16dp 41 | 16dp 42 | 43 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/model/MutableMediaMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.model; 18 | 19 | import android.support.v4.media.MediaMetadataCompat; 20 | import android.text.TextUtils; 21 | 22 | /** 23 | * Holder class that encapsulates a MediaMetadata and allows the actual metadata to be modified 24 | * without requiring to rebuild the collections the metadata is in. 25 | */ 26 | public class MutableMediaMetadata { 27 | 28 | public MediaMetadataCompat metadata; 29 | public final String trackId; 30 | 31 | public MutableMediaMetadata(String trackId, MediaMetadataCompat metadata) { 32 | this.metadata = metadata; 33 | this.trackId = trackId; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || o.getClass() != MutableMediaMetadata.class) { 42 | return false; 43 | } 44 | 45 | MutableMediaMetadata that = (MutableMediaMetadata) o; 46 | 47 | return TextUtils.equals(trackId, that.trackId); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return trackId.hashCode(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/ResourceHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.utils; 18 | 19 | import android.content.Context; 20 | import android.content.pm.ApplicationInfo; 21 | import android.content.pm.PackageManager; 22 | import android.content.res.Resources; 23 | import android.content.res.TypedArray; 24 | 25 | /** 26 | * Generic reusable methods to handle resources. 27 | */ 28 | public class ResourceHelper { 29 | /** 30 | * Get a color value from a theme attribute. 31 | * @param context used for getting the color. 32 | * @param attribute theme attribute. 33 | * @param defaultColor default to use. 34 | * @return color value 35 | */ 36 | public static int getThemeColor(Context context, int attribute, int defaultColor) { 37 | int themeColor = 0; 38 | String packageName = context.getPackageName(); 39 | try { 40 | Context packageContext = context.createPackageContext(packageName, 0); 41 | ApplicationInfo applicationInfo = 42 | context.getPackageManager().getApplicationInfo(packageName, 0); 43 | packageContext.setTheme(applicationInfo.theme); 44 | Resources.Theme theme = packageContext.getTheme(); 45 | TypedArray ta = theme.obtainStyledAttributes(new int[] {attribute}); 46 | themeColor = ta.getColor(0, defaultColor); 47 | ta.recycle(); 48 | } catch (PackageManager.NameNotFoundException e) { 49 | e.printStackTrace(); 50 | } 51 | return themeColor; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/NowPlayingActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui; 17 | 18 | import android.app.Activity; 19 | import android.app.UiModeManager; 20 | import android.content.Intent; 21 | import android.content.res.Configuration; 22 | import android.os.Bundle; 23 | 24 | import com.example.android.uamp.ui.tv.TvPlaybackActivity; 25 | import com.example.android.uamp.utils.LogHelper; 26 | 27 | /** 28 | * The activity for the Now Playing Card PendingIntent. 29 | * https://developer.android.com/training/tv/playback/now-playing.html 30 | * 31 | * This activity determines which activity to launch based on the current UI mode. 32 | */ 33 | public class NowPlayingActivity extends Activity { 34 | 35 | private static final String TAG = LogHelper.makeLogTag(NowPlayingActivity.class); 36 | 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | LogHelper.d(TAG, "onCreate"); 41 | Intent newIntent; 42 | UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); 43 | if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 44 | LogHelper.d(TAG, "Running on a TV Device"); 45 | newIntent = new Intent(this, TvPlaybackActivity.class); 46 | } else { 47 | LogHelper.d(TAG, "Running on a non-TV Device"); 48 | newIntent = new Intent(this, MusicPlayerActivity.class); 49 | } 50 | startActivity(newIntent); 51 | finish(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 25 | 26 | 27 | 43 | 44 | 51 | 52 | 53 | 55 | 56 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/com/example/android/uamp/playback/SimplePlayback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.playback; 17 | 18 | import android.support.v4.media.session.MediaSessionCompat; 19 | 20 | class SimplePlayback implements Playback { 21 | @Override 22 | public void start() { 23 | 24 | } 25 | 26 | @Override 27 | public void stop(boolean notifyListeners) { 28 | 29 | } 30 | 31 | @Override 32 | public void setState(int state) { 33 | 34 | } 35 | 36 | @Override 37 | public int getState() { 38 | return 0; 39 | } 40 | 41 | @Override 42 | public boolean isConnected() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean isPlaying() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public int getCurrentStreamPosition() { 53 | return 0; 54 | } 55 | 56 | @Override 57 | public void setCurrentStreamPosition(int pos) { 58 | 59 | } 60 | 61 | @Override 62 | public void updateLastKnownStreamPosition() { 63 | 64 | } 65 | 66 | @Override 67 | public void play(MediaSessionCompat.QueueItem item) { 68 | 69 | } 70 | 71 | @Override 72 | public void pause() { 73 | 74 | } 75 | 76 | @Override 77 | public void seekTo(int position) { 78 | 79 | } 80 | 81 | @Override 82 | public void setCurrentMediaId(String mediaId) { 83 | 84 | } 85 | 86 | @Override 87 | public String getCurrentMediaId() { 88 | return null; 89 | } 90 | 91 | @Override 92 | public void setCallback(Callback callback) { 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/fragment_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 29 | 42 | 43 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /mobile/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | apply plugin: 'com.android.application' 17 | 18 | android { 19 | compileSdkVersion 23 20 | buildToolsVersion "23.0.3" 21 | 22 | defaultConfig { 23 | applicationId "com.example.android.uamp" 24 | minSdkVersion 17 25 | targetSdkVersion 23 26 | versionCode 2 27 | versionName "1.1" 28 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_7 32 | targetCompatibility JavaVersion.VERSION_1_7 33 | } 34 | lintOptions { 35 | abortOnError true 36 | } 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | } 44 | 45 | repositories { 46 | flatDir { 47 | dirs 'libs' 48 | } 49 | } 50 | 51 | dependencies { 52 | compile 'com.google.android.gms:play-services-cast-framework:9.4.0' 53 | compile 'com.google.android.support:wearable:1.3.0' 54 | compile 'com.android.support:appcompat-v7:23.4.0' 55 | compile 'com.android.support:cardview-v7:23.4.0' 56 | compile 'com.android.support:mediarouter-v7:23.4.0' 57 | compile 'com.android.support:leanback-v17:23.4.0' 58 | compile 'com.android.support:design:23.4.0' 59 | testCompile 'junit:junit:4.12' 60 | testCompile 'org.mockito:mockito-core:1.10.19' 61 | androidTestCompile 'junit:junit:4.12' 62 | androidTestCompile 'com.android.support:support-annotations:23.4.0' 63 | androidTestCompile 'com.android.support.test:runner:0.4.1' 64 | androidTestCompile 'com.android.support.test:rules:0.4.1' 65 | } 66 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/com/example/android/uamp/utils/SimpleMusicProviderSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.utils; 18 | 19 | import android.support.v4.media.MediaMetadataCompat; 20 | 21 | import com.example.android.uamp.model.MusicProviderSource; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Iterator; 25 | import java.util.List; 26 | 27 | public class SimpleMusicProviderSource implements MusicProviderSource { 28 | 29 | private List mData = new ArrayList<>(); 30 | 31 | public void add(String title, String album, String artist, String genre, String source, 32 | String iconUrl, long trackNumber, long totalTrackCount, long durationMs) { 33 | String id = String.valueOf(source.hashCode()); 34 | 35 | //noinspection ResourceType 36 | mData.add(new MediaMetadataCompat.Builder() 37 | .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id) 38 | .putString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE, source) 39 | .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album) 40 | .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) 41 | .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs) 42 | .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre) 43 | .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl) 44 | .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) 45 | .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber) 46 | .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount) 47 | .build()); 48 | } 49 | 50 | @Override 51 | public Iterator iterator() { 52 | return mData.iterator(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/WearHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import android.os.Bundle; 19 | import android.support.wearable.media.MediaControlConstants; 20 | 21 | public class WearHelper { 22 | private static final String WEAR_APP_PACKAGE_NAME = "com.google.android.wearable.app"; 23 | 24 | public static boolean isValidWearCompanionPackage(String packageName) { 25 | return WEAR_APP_PACKAGE_NAME.equals(packageName); 26 | } 27 | 28 | public static void setShowCustomActionOnWear(Bundle customActionExtras, boolean showOnWear) { 29 | if (showOnWear) { 30 | customActionExtras.putBoolean( 31 | MediaControlConstants.EXTRA_CUSTOM_ACTION_SHOW_ON_WEAR, true); 32 | } else { 33 | customActionExtras.remove(MediaControlConstants.EXTRA_CUSTOM_ACTION_SHOW_ON_WEAR); 34 | } 35 | } 36 | 37 | public static void setUseBackgroundFromTheme(Bundle extras, boolean useBgFromTheme) { 38 | if (useBgFromTheme) { 39 | extras.putBoolean(MediaControlConstants.EXTRA_BACKGROUND_COLOR_FROM_THEME, true); 40 | } else { 41 | extras.remove(MediaControlConstants.EXTRA_BACKGROUND_COLOR_FROM_THEME); 42 | } 43 | } 44 | 45 | public static void setSlotReservationFlags(Bundle extras, boolean reserveSkipToNextSlot, 46 | boolean reserveSkipToPrevSlot) { 47 | if (reserveSkipToPrevSlot) { 48 | extras.putBoolean(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_PREVIOUS, true); 49 | } else { 50 | extras.remove(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_PREVIOUS); 51 | } 52 | if (reserveSkipToNextSlot) { 53 | extras.putBoolean(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_NEXT, true); 54 | } else { 55 | extras.remove(MediaControlConstants.EXTRA_RESERVE_SLOT_SKIP_TO_NEXT); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Universal Android Music Player Sample 2 | ===================================== 3 | 4 | This sample shows how to implement an audio media app that works 5 | across multiple form factors and provide a consistent user experience 6 | on Android phones, tablets, Android Auto, Android Wear, Android TV and Google Cast devices. 7 | 8 | 9 | Pre-requisites 10 | -------------- 11 | 12 | - Android SDK v17 13 | 14 | Getting Started 15 | --------------- 16 | 17 | This sample uses the Gradle build system. To build this project, use the 18 | "gradlew build" command or use "Import Project" in Android Studio. 19 | 20 | Screenshots 21 | ----------- 22 | 23 | ![Phone](screenshots/phone.png "On a phone") 24 | ![Lock screen](screenshots/phone_lockscreen.png "Lockscreen background and controls") 25 | ![Full screen player](screenshots/phone_fullscreen_player.png "A basic full screen activity") 26 | ![Cast dialog](screenshots/phone_cast_dialog.png "Casting to Google Cast devices") 27 | ![Android Auto](screenshots/android_auto.png "Running on an Android Auto car") 28 | ![Android TV](screenshots/android_tv.png "Running on an Android TV") 29 | 30 | ![Android Wear watch face](screenshots/android_wear_1.png "MediaStyle notifications on an Android Wear watch") 31 | ![Android Wear controls](screenshots/android_wear_2.png "Media playback controls on an Android Wear watch") 32 | 33 | Support 34 | ------- 35 | 36 | - Google+ Community: https://plus.google.com/communities/105153134372062985968 37 | - Stack Overflow: http://stackoverflow.com/questions/tagged/android 38 | 39 | If you've found an error in this sample, please file an issue: 40 | https://github.com/googlesamples/android-UniversalMusicPlayer/issues 41 | 42 | Patches are encouraged, and may be submitted by forking this project and 43 | submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details. 44 | 45 | License 46 | ------- 47 | 48 | Copyright 2014 The Android Open Source Project, Inc. 49 | 50 | Licensed to the Apache Software Foundation (ASF) under one or more contributor 51 | license agreements. See the NOTICE file distributed with this work for 52 | additional information regarding copyright ownership. The ASF licenses this 53 | file to you under the Apache License, Version 2.0 (the "License"); you may not 54 | use this file except in compliance with the License. You may obtain a copy of 55 | the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, software 60 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 61 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 62 | License for the specific language governing permissions and limitations under 63 | the License. 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/activity_player.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 26 | 27 | 30 | 31 | 32 | 33 | 39 | 40 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/media_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 33 | 34 | 41 | 42 | 51 | 52 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/activity_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 26 | 27 | 30 | 31 | 32 | 33 | 41 | 42 | 48 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Universal Music Player 20 | Favorite 21 | Unable to retrieve metadata. 22 | Genres 23 | Songs by genre 24 | %1$s songs 25 | Random music 26 | Cannot connect to server. Please, check your Internet connectivity. 27 | Error Loading Media 28 | Casting to %1$s 29 | Song currently playing 30 | Artist of currently playing song 31 | Extra info for currently playing song 32 | Play item 33 | play or pause 34 | skip to next 35 | skip to previous 36 | Touch to connect to a Google Cast device 37 | 39 | CC1AD845 40 | Home 41 | You may also like 42 | Listen 43 | Open the main menu 44 | Close the main menu 45 | This is a placeholder for your application code. 46 | All Music 47 | Playlists 48 | Now Playing 49 | Background image for album art 50 | Search results 51 | No search results. 52 | Loading… 53 | Play on… 54 | 55 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/LogHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import android.util.Log; 19 | 20 | import com.example.android.uamp.BuildConfig; 21 | 22 | public class LogHelper { 23 | 24 | private static final String LOG_PREFIX = "uamp_"; 25 | private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length(); 26 | private static final int MAX_LOG_TAG_LENGTH = 23; 27 | 28 | public static String makeLogTag(String str) { 29 | if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) { 30 | return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1); 31 | } 32 | 33 | return LOG_PREFIX + str; 34 | } 35 | 36 | /** 37 | * Don't use this when obfuscating class names! 38 | */ 39 | public static String makeLogTag(Class cls) { 40 | return makeLogTag(cls.getSimpleName()); 41 | } 42 | 43 | 44 | public static void v(String tag, Object... messages) { 45 | // Only log VERBOSE if build type is DEBUG 46 | if (BuildConfig.DEBUG) { 47 | log(tag, Log.VERBOSE, null, messages); 48 | } 49 | } 50 | 51 | public static void d(String tag, Object... messages) { 52 | // Only log DEBUG if build type is DEBUG 53 | if (BuildConfig.DEBUG) { 54 | log(tag, Log.DEBUG, null, messages); 55 | } 56 | } 57 | 58 | public static void i(String tag, Object... messages) { 59 | log(tag, Log.INFO, null, messages); 60 | } 61 | 62 | public static void w(String tag, Object... messages) { 63 | log(tag, Log.WARN, null, messages); 64 | } 65 | 66 | public static void w(String tag, Throwable t, Object... messages) { 67 | log(tag, Log.WARN, t, messages); 68 | } 69 | 70 | public static void e(String tag, Object... messages) { 71 | log(tag, Log.ERROR, null, messages); 72 | } 73 | 74 | public static void e(String tag, Throwable t, Object... messages) { 75 | log(tag, Log.ERROR, t, messages); 76 | } 77 | 78 | public static void log(String tag, int level, Throwable t, Object... messages) { 79 | if (Log.isLoggable(tag, level)) { 80 | String message; 81 | if (t == null && messages != null && messages.length == 1) { 82 | // handle this common case without the extra cost of creating a stringbuffer: 83 | message = messages[0].toString(); 84 | } else { 85 | StringBuilder sb = new StringBuilder(); 86 | if (messages != null) for (Object m : messages) { 87 | sb.append(m); 88 | } 89 | if (t != null) { 90 | sb.append("\n").append(Log.getStackTraceString(t)); 91 | } 92 | message = sb.toString(); 93 | } 94 | Log.println(level, tag, message); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/BitmapHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import android.graphics.Bitmap; 19 | import android.graphics.BitmapFactory; 20 | 21 | import java.io.BufferedInputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | 27 | public class BitmapHelper { 28 | private static final String TAG = LogHelper.makeLogTag(BitmapHelper.class); 29 | 30 | // Max read limit that we allow our input stream to mark/reset. 31 | private static final int MAX_READ_LIMIT_PER_IMG = 1024 * 1024; 32 | 33 | public static Bitmap scaleBitmap(Bitmap src, int maxWidth, int maxHeight) { 34 | double scaleFactor = Math.min( 35 | ((double) maxWidth)/src.getWidth(), ((double) maxHeight)/src.getHeight()); 36 | return Bitmap.createScaledBitmap(src, 37 | (int) (src.getWidth() * scaleFactor), (int) (src.getHeight() * scaleFactor), false); 38 | } 39 | 40 | public static Bitmap scaleBitmap(int scaleFactor, InputStream is) { 41 | // Get the dimensions of the bitmap 42 | BitmapFactory.Options bmOptions = new BitmapFactory.Options(); 43 | 44 | // Decode the image file into a Bitmap sized to fill the View 45 | bmOptions.inJustDecodeBounds = false; 46 | bmOptions.inSampleSize = scaleFactor; 47 | 48 | return BitmapFactory.decodeStream(is, null, bmOptions); 49 | } 50 | 51 | public static int findScaleFactor(int targetW, int targetH, InputStream is) { 52 | // Get the dimensions of the bitmap 53 | BitmapFactory.Options bmOptions = new BitmapFactory.Options(); 54 | bmOptions.inJustDecodeBounds = true; 55 | BitmapFactory.decodeStream(is, null, bmOptions); 56 | int actualW = bmOptions.outWidth; 57 | int actualH = bmOptions.outHeight; 58 | 59 | // Determine how much to scale down the image 60 | return Math.min(actualW/targetW, actualH/targetH); 61 | } 62 | 63 | @SuppressWarnings("SameParameterValue") 64 | public static Bitmap fetchAndRescaleBitmap(String uri, int width, int height) 65 | throws IOException { 66 | URL url = new URL(uri); 67 | BufferedInputStream is = null; 68 | try { 69 | HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 70 | is = new BufferedInputStream(urlConnection.getInputStream()); 71 | is.mark(MAX_READ_LIMIT_PER_IMG); 72 | int scaleFactor = findScaleFactor(width, height, is); 73 | LogHelper.d(TAG, "Scaling bitmap ", uri, " by factor ", scaleFactor, " to support ", 74 | width, "x", height, "requested dimension"); 75 | is.reset(); 76 | return scaleBitmap(scaleFactor, is); 77 | } finally { 78 | if (is != null) { 79 | is.close(); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/fragment_playback_controls.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 30 | 39 | 49 | 58 | 68 | 69 | 81 | 82 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/playback/Playback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.playback; 17 | 18 | import com.example.android.uamp.MusicService; 19 | 20 | import static android.support.v4.media.session.MediaSessionCompat.QueueItem; 21 | 22 | /** 23 | * Interface representing either Local or Remote Playback. The {@link MusicService} works 24 | * directly with an instance of the Playback object to make the various calls such as 25 | * play, pause etc. 26 | */ 27 | public interface Playback { 28 | /** 29 | * Start/setup the playback. 30 | * Resources/listeners would be allocated by implementations. 31 | */ 32 | void start(); 33 | 34 | /** 35 | * Stop the playback. All resources can be de-allocated by implementations here. 36 | * @param notifyListeners if true and a callback has been set by setCallback, 37 | * callback.onPlaybackStatusChanged will be called after changing 38 | * the state. 39 | */ 40 | void stop(boolean notifyListeners); 41 | 42 | /** 43 | * Set the latest playback state as determined by the caller. 44 | */ 45 | void setState(int state); 46 | 47 | /** 48 | * Get the current {@link android.media.session.PlaybackState#getState()} 49 | */ 50 | int getState(); 51 | 52 | /** 53 | * @return boolean that indicates that this is ready to be used. 54 | */ 55 | boolean isConnected(); 56 | 57 | /** 58 | * @return boolean indicating whether the player is playing or is supposed to be 59 | * playing when we gain audio focus. 60 | */ 61 | boolean isPlaying(); 62 | 63 | /** 64 | * @return pos if currently playing an item 65 | */ 66 | int getCurrentStreamPosition(); 67 | 68 | /** 69 | * Set the current position. Typically used when switching players that are in 70 | * paused state. 71 | * 72 | * @param pos position in the stream 73 | */ 74 | void setCurrentStreamPosition(int pos); 75 | 76 | /** 77 | * Query the underlying stream and update the internal last known stream position. 78 | */ 79 | void updateLastKnownStreamPosition(); 80 | 81 | /** 82 | * @param item to play 83 | */ 84 | void play(QueueItem item); 85 | 86 | /** 87 | * Pause the current playing item 88 | */ 89 | void pause(); 90 | 91 | /** 92 | * Seek to the given position 93 | */ 94 | void seekTo(int position); 95 | 96 | /** 97 | * Set the current mediaId. This is only used when switching from one 98 | * playback to another. 99 | * 100 | * @param mediaId to be set as the current. 101 | */ 102 | void setCurrentMediaId(String mediaId); 103 | 104 | /** 105 | * 106 | * @return the current media Id being processed in any state or null. 107 | */ 108 | String getCurrentMediaId(); 109 | 110 | interface Callback { 111 | /** 112 | * On current music completed. 113 | */ 114 | void onCompletion(); 115 | /** 116 | * on Playback status changed 117 | * Implementations can use this callback to update 118 | * playback state on the media sessions. 119 | */ 120 | void onPlaybackStatusChanged(int state); 121 | 122 | /** 123 | * @param error to be added to the PlaybackState 124 | */ 125 | void onError(String error); 126 | 127 | /** 128 | * @param mediaId being currently played 129 | */ 130 | void setCurrentMediaId(String mediaId); 131 | } 132 | 133 | /** 134 | * @param callback to be called 135 | */ 136 | void setCallback(Callback callback); 137 | } 138 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/tv/TvVerticalGridActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui.tv; 17 | 18 | import android.content.ComponentName; 19 | import android.os.Bundle; 20 | import android.os.RemoteException; 21 | import android.support.v4.app.FragmentActivity; 22 | import android.support.v4.media.MediaBrowserCompat; 23 | import android.support.v4.media.session.MediaControllerCompat; 24 | 25 | import com.example.android.uamp.MusicService; 26 | import com.example.android.uamp.R; 27 | import com.example.android.uamp.utils.LogHelper; 28 | 29 | public class TvVerticalGridActivity extends FragmentActivity 30 | implements TvVerticalGridFragment.MediaFragmentListener { 31 | 32 | private static final String TAG = LogHelper.makeLogTag(TvVerticalGridActivity.class); 33 | public static final String SHARED_ELEMENT_NAME = "hero"; 34 | private MediaBrowserCompat mMediaBrowser; 35 | private String mMediaId; 36 | private String mTitle; 37 | 38 | @Override 39 | public void onCreate(Bundle savedInstanceState) 40 | { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.tv_vertical_grid); 43 | 44 | mMediaId = getIntent().getStringExtra(TvBrowseActivity.SAVED_MEDIA_ID); 45 | mTitle = getIntent().getStringExtra(TvBrowseActivity.BROWSE_TITLE); 46 | 47 | getWindow().setBackgroundDrawableResource(R.drawable.bg); 48 | 49 | mMediaBrowser = new MediaBrowserCompat(this, 50 | new ComponentName(this, MusicService.class), 51 | mConnectionCallback, null); 52 | } 53 | 54 | @Override 55 | protected void onStart() { 56 | super.onStart(); 57 | LogHelper.d(TAG, "Activity onStart: mMediaBrowser connect"); 58 | mMediaBrowser.connect(); 59 | } 60 | 61 | @Override 62 | protected void onStop() { 63 | super.onStop(); 64 | mMediaBrowser.disconnect(); 65 | } 66 | 67 | protected void browse() { 68 | LogHelper.d(TAG, "navigateToBrowser, mediaId=" + mMediaId); 69 | TvVerticalGridFragment fragment = (TvVerticalGridFragment) getSupportFragmentManager() 70 | .findFragmentById(R.id.vertical_grid_fragment); 71 | fragment.setMediaId(mMediaId); 72 | fragment.setTitle(mTitle); 73 | } 74 | 75 | @Override 76 | public MediaBrowserCompat getMediaBrowser() { 77 | return mMediaBrowser; 78 | } 79 | 80 | private final MediaBrowserCompat.ConnectionCallback mConnectionCallback = 81 | new MediaBrowserCompat.ConnectionCallback() { 82 | @Override 83 | public void onConnected() { 84 | LogHelper.d(TAG, "onConnected: session token ", 85 | mMediaBrowser.getSessionToken()); 86 | 87 | try { 88 | MediaControllerCompat mediaController = new MediaControllerCompat( 89 | TvVerticalGridActivity.this, mMediaBrowser.getSessionToken()); 90 | setSupportMediaController(mediaController); 91 | browse(); 92 | } catch (RemoteException e) { 93 | LogHelper.e(TAG, e, "could not connect media controller"); 94 | } 95 | } 96 | 97 | @Override 98 | public void onConnectionFailed() { 99 | LogHelper.d(TAG, "onConnectionFailed"); 100 | } 101 | 102 | @Override 103 | public void onConnectionSuspended() { 104 | LogHelper.d(TAG, "onConnectionSuspended"); 105 | setSupportMediaController(null); 106 | } 107 | }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /mobile/src/test/java/com/example/android/uamp/utils/MediaIDHelperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | import static org.junit.Assert.assertArrayEquals; 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertNull; 25 | import static org.junit.Assert.fail; 26 | 27 | /** 28 | * Unit tests for the {@link MediaIDHelper} class. Exercises the helper methods that 29 | * do MediaID to MusicID conversion and hierarchy (categories) extraction. 30 | */ 31 | @RunWith(JUnit4.class) 32 | public class MediaIDHelperTest { 33 | 34 | @Test 35 | public void testNormalMediaIDStructure() throws Exception { 36 | String mediaID = MediaIDHelper.createMediaID("784343", "BY_GENRE", "Classic 70's"); 37 | assertEquals("Classic 70's", MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaID)); 38 | assertEquals("784343", MediaIDHelper.extractMusicIDFromMediaID(mediaID)); 39 | } 40 | 41 | @Test 42 | public void testSpecialSymbolsMediaIDStructure() throws Exception { 43 | String mediaID = MediaIDHelper.createMediaID("78A_88|X/3", "BY_GENRE", "Classic 70's"); 44 | assertEquals("Classic 70's", MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaID)); 45 | assertEquals("78A_88|X/3", MediaIDHelper.extractMusicIDFromMediaID(mediaID)); 46 | } 47 | 48 | @Test 49 | public void testNullMediaIDStructure() throws Exception { 50 | String mediaID = MediaIDHelper.createMediaID(null, "BY_GENRE", "Classic 70's"); 51 | assertEquals("Classic 70's", MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaID)); 52 | assertNull(MediaIDHelper.extractMusicIDFromMediaID(mediaID)); 53 | } 54 | 55 | @Test(expected = IllegalArgumentException.class) 56 | public void testInvalidSymbolsInMediaIDStructure() throws Exception { 57 | fail(MediaIDHelper.createMediaID(null, "BY|GENRE/2", "Classic 70's")); 58 | } 59 | 60 | @Test 61 | public void testCreateBrowseCategoryMediaID() throws Exception { 62 | String browseMediaID = MediaIDHelper.createMediaID(null, "BY_GENRE", "Rock & Roll"); 63 | assertEquals("Rock & Roll", MediaIDHelper.extractBrowseCategoryValueFromMediaID(browseMediaID)); 64 | String[] categories = MediaIDHelper.getHierarchy(browseMediaID); 65 | assertArrayEquals(categories, new String[]{"BY_GENRE", "Rock & Roll"}); 66 | } 67 | 68 | @Test 69 | public void testGetParentOfPlayableMediaID() throws Exception { 70 | String mediaID = MediaIDHelper.createMediaID("23423423", "BY_GENRE", "Rock & Roll"); 71 | String expectedParentID = MediaIDHelper.createMediaID(null, "BY_GENRE", "Rock & Roll"); 72 | assertEquals(expectedParentID, MediaIDHelper.getParentMediaID(mediaID)); 73 | } 74 | 75 | @Test 76 | public void testGetParentOfBrowsableMediaID() throws Exception { 77 | String mediaID = MediaIDHelper.createMediaID(null, "BY_GENRE", "Rock & Roll"); 78 | String expectedParentID = MediaIDHelper.createMediaID(null, "BY_GENRE"); 79 | assertEquals(expectedParentID, MediaIDHelper.getParentMediaID(mediaID)); 80 | } 81 | 82 | @Test 83 | public void testGetParentOfCategoryMediaID() throws Exception { 84 | assertEquals( 85 | MediaIDHelper.MEDIA_ID_ROOT, 86 | MediaIDHelper.getParentMediaID(MediaIDHelper.createMediaID(null, "BY_GENRE"))); 87 | } 88 | 89 | @Test 90 | public void testGetParentOfRoot() throws Exception { 91 | assertEquals( 92 | MediaIDHelper.MEDIA_ID_ROOT, 93 | MediaIDHelper.getParentMediaID(MediaIDHelper.MEDIA_ID_ROOT)); 94 | } 95 | 96 | @Test(expected=NullPointerException.class) 97 | public void testGetParentOfNull() throws Exception { 98 | //noinspection ConstantConditions 99 | fail(MediaIDHelper.getParentMediaID(null)); 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/tv/TvBrowseActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui.tv; 17 | 18 | import android.content.ComponentName; 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.os.RemoteException; 22 | import android.support.annotation.NonNull; 23 | import android.support.v4.app.FragmentActivity; 24 | import android.support.v4.media.MediaBrowserCompat; 25 | import android.support.v4.media.session.MediaControllerCompat; 26 | 27 | import com.example.android.uamp.MusicService; 28 | import com.example.android.uamp.R; 29 | import com.example.android.uamp.utils.LogHelper; 30 | 31 | /** 32 | * Main activity for the Android TV user interface. 33 | */ 34 | public class TvBrowseActivity extends FragmentActivity 35 | implements TvBrowseFragment.MediaFragmentListener { 36 | 37 | private static final String TAG = LogHelper.makeLogTag(TvBrowseActivity.class); 38 | public static final String SAVED_MEDIA_ID="com.example.android.uamp.MEDIA_ID"; 39 | public static final String BROWSE_TITLE = "com.example.android.uamp.BROWSE_TITLE"; 40 | 41 | private MediaBrowserCompat mMediaBrowser; 42 | 43 | private String mMediaId; 44 | private String mBrowseTitle; 45 | 46 | @Override 47 | public void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | LogHelper.d(TAG, "Activity onCreate"); 50 | 51 | setContentView(R.layout.tv_activity_player); 52 | 53 | mMediaBrowser = new MediaBrowserCompat(this, 54 | new ComponentName(this, MusicService.class), 55 | mConnectionCallback, null); 56 | } 57 | 58 | @Override 59 | protected void onSaveInstanceState(@NonNull Bundle outState) { 60 | if (mMediaId != null) { 61 | outState.putString(SAVED_MEDIA_ID, mMediaId); 62 | outState.putString(BROWSE_TITLE, mBrowseTitle); 63 | } 64 | super.onSaveInstanceState(outState); 65 | } 66 | 67 | @Override 68 | protected void onStart() { 69 | super.onStart(); 70 | LogHelper.d(TAG, "Activity onStart"); 71 | mMediaBrowser.connect(); 72 | } 73 | 74 | @Override 75 | protected void onStop() { 76 | super.onStop(); 77 | LogHelper.d(TAG, "Activity onStop"); 78 | if (mMediaBrowser != null) { 79 | mMediaBrowser.disconnect(); 80 | } 81 | } 82 | 83 | @Override 84 | public boolean onSearchRequested() { 85 | startActivity(new Intent(this, TvBrowseActivity.class)); 86 | return true; 87 | } 88 | 89 | protected void navigateToBrowser(String mediaId) { 90 | LogHelper.d(TAG, "navigateToBrowser, mediaId=" + mediaId); 91 | TvBrowseFragment fragment = 92 | (TvBrowseFragment) getSupportFragmentManager().findFragmentById(R.id.main_browse_fragment); 93 | fragment.initializeWithMediaId(mediaId); 94 | mMediaId = mediaId; 95 | if (mediaId == null) { 96 | mBrowseTitle = getResources().getString(R.string.home_title); 97 | } 98 | fragment.setTitle(mBrowseTitle); 99 | } 100 | 101 | @Override 102 | public MediaBrowserCompat getMediaBrowser() { 103 | return mMediaBrowser; 104 | } 105 | 106 | private final MediaBrowserCompat.ConnectionCallback mConnectionCallback = 107 | new MediaBrowserCompat.ConnectionCallback() { 108 | @Override 109 | public void onConnected() { 110 | LogHelper.d(TAG, "onConnected: session token ", 111 | mMediaBrowser.getSessionToken()); 112 | try { 113 | MediaControllerCompat mediaController = new MediaControllerCompat( 114 | TvBrowseActivity.this, mMediaBrowser.getSessionToken()); 115 | setSupportMediaController(mediaController); 116 | navigateToBrowser(mMediaId); 117 | } catch (RemoteException e) { 118 | LogHelper.e(TAG, e, "could not connect media controller"); 119 | } 120 | } 121 | 122 | @Override 123 | public void onConnectionFailed() { 124 | LogHelper.d(TAG, "onConnectionFailed"); 125 | } 126 | 127 | @Override 128 | public void onConnectionSuspended() { 129 | LogHelper.d(TAG, "onConnectionSuspended"); 130 | setSupportMediaController(null); 131 | } 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/VoiceSearchParams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp; 17 | 18 | import android.os.Build; 19 | import android.os.Bundle; 20 | import android.provider.MediaStore; 21 | import android.text.TextUtils; 22 | 23 | /** 24 | * For more information about voice search parameters, 25 | * check https://developer.android.com/guide/components/intents-common.html#PlaySearch 26 | */ 27 | public final class VoiceSearchParams { 28 | 29 | public final String query; 30 | public boolean isAny; 31 | public boolean isUnstructured; 32 | public boolean isGenreFocus; 33 | public boolean isArtistFocus; 34 | public boolean isAlbumFocus; 35 | public boolean isSongFocus; 36 | public String genre; 37 | public String artist; 38 | public String album; 39 | public String song; 40 | 41 | /** 42 | * Creates a simple object describing the search criteria from the query and extras. 43 | * @param query the query parameter from a voice search 44 | * @param extras the extras parameter from a voice search 45 | */ 46 | public VoiceSearchParams(String query, Bundle extras) { 47 | this.query = query; 48 | 49 | if (TextUtils.isEmpty(query)) { 50 | // A generic search like "Play music" sends an empty query 51 | isAny = true; 52 | } else { 53 | if (extras == null) { 54 | isUnstructured = true; 55 | } else { 56 | String genreKey; 57 | if (Build.VERSION.SDK_INT >= 21) { 58 | genreKey = MediaStore.EXTRA_MEDIA_GENRE; 59 | } else { 60 | genreKey = "android.intent.extra.genre"; 61 | } 62 | 63 | String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS); 64 | if (TextUtils.equals(mediaFocus, MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE)) { 65 | // for a Genre focused search, only genre is set: 66 | isGenreFocus = true; 67 | genre = extras.getString(genreKey); 68 | if (TextUtils.isEmpty(genre)) { 69 | // Because of a bug on the platform, genre is only sent as a query, not as 70 | // the semantic-aware extras. This check makes it future-proof when the 71 | // bug is fixed. 72 | genre = query; 73 | } 74 | } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { 75 | // for an Artist focused search, both artist and genre are set: 76 | isArtistFocus = true; 77 | genre = extras.getString(genreKey); 78 | artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); 79 | } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { 80 | // for an Album focused search, album, artist and genre are set: 81 | isAlbumFocus = true; 82 | album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM); 83 | genre = extras.getString(genreKey); 84 | artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); 85 | } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Media.ENTRY_CONTENT_TYPE)) { 86 | // for a Song focused search, title, album, artist and genre are set: 87 | isSongFocus = true; 88 | song = extras.getString(MediaStore.EXTRA_MEDIA_TITLE); 89 | album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM); 90 | genre = extras.getString(genreKey); 91 | artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); 92 | } else { 93 | // If we don't know the focus, we treat it is an unstructured query: 94 | isUnstructured = true; 95 | } 96 | } 97 | } 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "query=" + query 103 | + " isAny=" + isAny 104 | + " isUnstructured=" + isUnstructured 105 | + " isGenreFocus=" + isGenreFocus 106 | + " isArtistFocus=" + isArtistFocus 107 | + " isAlbumFocus=" + isAlbumFocus 108 | + " isSongFocus=" + isSongFocus 109 | + " genre=" + genre 110 | + " artist=" + artist 111 | + " album=" + album 112 | + " song=" + song; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/CarHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.utils; 17 | 18 | import android.app.UiModeManager; 19 | import android.content.Context; 20 | import android.content.res.Configuration; 21 | import android.os.Bundle; 22 | 23 | import com.example.android.uamp.MusicService; 24 | 25 | public class CarHelper { 26 | private static final String TAG = LogHelper.makeLogTag(CarHelper.class); 27 | 28 | private static final String AUTO_APP_PACKAGE_NAME = "com.google.android.projection.gearhead"; 29 | 30 | // Use these extras to reserve space for the corresponding actions, even when they are disabled 31 | // in the playbackstate, so the custom actions don't reflow. 32 | private static final String SLOT_RESERVATION_SKIP_TO_NEXT = 33 | "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; 34 | private static final String SLOT_RESERVATION_SKIP_TO_PREV = 35 | "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; 36 | private static final String SLOT_RESERVATION_QUEUE = 37 | "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"; 38 | 39 | /** 40 | * Action for an intent broadcast by Android Auto when a media app is connected or 41 | * disconnected. A "connected" media app is the one currently attached to the "media" facet 42 | * on Android Auto. So, this intent is sent by AA on: 43 | * 44 | * - connection: when the phone is projecting and at the moment the app is selected from the 45 | * list of media apps 46 | * - disconnection: when another media app is selected from the list of media apps or when 47 | * the phone stops projecting (when the user unplugs it, for example) 48 | * 49 | * The actual event (connected or disconnected) will come as an Intent extra, 50 | * with the key MEDIA_CONNECTION_STATUS (see below). 51 | */ 52 | public static final String ACTION_MEDIA_STATUS = "com.google.android.gms.car.media.STATUS"; 53 | 54 | /** 55 | * Key in Intent extras that contains the media connection event type (connected or disconnected) 56 | */ 57 | public static final String MEDIA_CONNECTION_STATUS = "media_connection_status"; 58 | 59 | /** 60 | * Value of the key MEDIA_CONNECTION_STATUS in Intent extras used when the current media app 61 | * is connected. 62 | */ 63 | public static final String MEDIA_CONNECTED = "media_connected"; 64 | 65 | /** 66 | * Value of the key MEDIA_CONNECTION_STATUS in Intent extras used when the current media app 67 | * is disconnected. 68 | */ 69 | public static final String MEDIA_DISCONNECTED = "media_disconnected"; 70 | 71 | 72 | public static boolean isValidCarPackage(String packageName) { 73 | return AUTO_APP_PACKAGE_NAME.equals(packageName); 74 | } 75 | 76 | public static void setSlotReservationFlags(Bundle extras, boolean reservePlayingQueueSlot, 77 | boolean reserveSkipToNextSlot, boolean reserveSkipToPrevSlot) { 78 | if (reservePlayingQueueSlot) { 79 | extras.putBoolean(SLOT_RESERVATION_QUEUE, true); 80 | } else { 81 | extras.remove(SLOT_RESERVATION_QUEUE); 82 | } 83 | if (reserveSkipToPrevSlot) { 84 | extras.putBoolean(SLOT_RESERVATION_SKIP_TO_PREV, true); 85 | } else { 86 | extras.remove(SLOT_RESERVATION_SKIP_TO_PREV); 87 | } 88 | if (reserveSkipToNextSlot) { 89 | extras.putBoolean(SLOT_RESERVATION_SKIP_TO_NEXT, true); 90 | } else { 91 | extras.remove(SLOT_RESERVATION_SKIP_TO_NEXT); 92 | } 93 | } 94 | 95 | /** 96 | * Returns true when running Android Auto or a car dock. 97 | * 98 | * A preferable way of detecting if your app is running in the context of an Android Auto 99 | * compatible car is by registering a BroadcastReceiver for the action 100 | * {@link CarHelper#ACTION_MEDIA_STATUS}. See a sample implementation in 101 | * {@link MusicService#onCreate()}. 102 | * 103 | * @param c Context to detect UI Mode. 104 | * @return true when device is running in car mode, false otherwise. 105 | */ 106 | public static boolean isCarUiMode(Context c) { 107 | UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE); 108 | if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { 109 | LogHelper.d(TAG, "Running in Car mode"); 110 | return true; 111 | } else { 112 | LogHelper.d(TAG, "Running on a non-Car mode"); 113 | return false; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/MediaItemViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.content.res.ColorStateList; 21 | import android.graphics.drawable.AnimationDrawable; 22 | import android.graphics.drawable.Drawable; 23 | import android.support.v4.content.ContextCompat; 24 | import android.support.v4.graphics.drawable.DrawableCompat; 25 | import android.support.v4.media.MediaDescriptionCompat; 26 | import android.view.LayoutInflater; 27 | import android.view.View; 28 | import android.view.ViewGroup; 29 | import android.widget.ImageView; 30 | import android.widget.TextView; 31 | 32 | import com.example.android.uamp.R; 33 | 34 | public class MediaItemViewHolder { 35 | 36 | static final int STATE_INVALID = -1; 37 | static final int STATE_NONE = 0; 38 | static final int STATE_PLAYABLE = 1; 39 | static final int STATE_PAUSED = 2; 40 | static final int STATE_PLAYING = 3; 41 | 42 | private static ColorStateList sColorStatePlaying; 43 | private static ColorStateList sColorStateNotPlaying; 44 | 45 | ImageView mImageView; 46 | TextView mTitleView; 47 | TextView mDescriptionView; 48 | 49 | static View setupView(Activity activity, View convertView, ViewGroup parent, 50 | MediaDescriptionCompat description, int state) { 51 | 52 | if (sColorStateNotPlaying == null || sColorStatePlaying == null) { 53 | initializeColorStateLists(activity); 54 | } 55 | 56 | MediaItemViewHolder holder; 57 | 58 | Integer cachedState = STATE_INVALID; 59 | 60 | if (convertView == null) { 61 | convertView = LayoutInflater.from(activity) 62 | .inflate(R.layout.media_list_item, parent, false); 63 | holder = new MediaItemViewHolder(); 64 | holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq); 65 | holder.mTitleView = (TextView) convertView.findViewById(R.id.title); 66 | holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description); 67 | convertView.setTag(holder); 68 | } else { 69 | holder = (MediaItemViewHolder) convertView.getTag(); 70 | cachedState = (Integer) convertView.getTag(R.id.tag_mediaitem_state_cache); 71 | } 72 | 73 | holder.mTitleView.setText(description.getTitle()); 74 | holder.mDescriptionView.setText(description.getSubtitle()); 75 | 76 | // If the state of convertView is different, we need to adapt the view to the 77 | // new state. 78 | if (cachedState == null || cachedState != state) { 79 | switch (state) { 80 | case STATE_PLAYABLE: 81 | Drawable pauseDrawable = ContextCompat.getDrawable(activity, 82 | R.drawable.ic_play_arrow_black_36dp); 83 | DrawableCompat.setTintList(pauseDrawable, sColorStateNotPlaying); 84 | holder.mImageView.setImageDrawable(pauseDrawable); 85 | holder.mImageView.setVisibility(View.VISIBLE); 86 | break; 87 | case STATE_PLAYING: 88 | AnimationDrawable animation = (AnimationDrawable) 89 | ContextCompat.getDrawable(activity, R.drawable.ic_equalizer_white_36dp); 90 | DrawableCompat.setTintList(animation, sColorStatePlaying); 91 | holder.mImageView.setImageDrawable(animation); 92 | holder.mImageView.setVisibility(View.VISIBLE); 93 | animation.start(); 94 | break; 95 | case STATE_PAUSED: 96 | Drawable playDrawable = ContextCompat.getDrawable(activity, 97 | R.drawable.ic_equalizer1_white_36dp); 98 | DrawableCompat.setTintList(playDrawable, sColorStatePlaying); 99 | holder.mImageView.setImageDrawable(playDrawable); 100 | holder.mImageView.setVisibility(View.VISIBLE); 101 | break; 102 | default: 103 | holder.mImageView.setVisibility(View.GONE); 104 | } 105 | convertView.setTag(R.id.tag_mediaitem_state_cache, state); 106 | } 107 | 108 | return convertView; 109 | } 110 | 111 | static private void initializeColorStateLists(Context ctx) { 112 | sColorStateNotPlaying = ColorStateList.valueOf(ctx.getResources().getColor( 113 | R.color.media_item_icon_not_playing)); 114 | sColorStatePlaying = ColorStateList.valueOf(ctx.getResources().getColor( 115 | R.color.media_item_icon_playing)); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/ui/tv/TvPlaybackActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.android.uamp.ui.tv; 17 | 18 | import android.content.ComponentName; 19 | import android.os.Bundle; 20 | import android.os.RemoteException; 21 | import android.support.annotation.NonNull; 22 | import android.support.v4.app.FragmentActivity; 23 | import android.support.v4.media.MediaBrowserCompat; 24 | import android.support.v4.media.MediaMetadataCompat; 25 | import android.support.v4.media.session.MediaControllerCompat; 26 | import android.support.v4.media.session.PlaybackStateCompat; 27 | 28 | import com.example.android.uamp.MusicService; 29 | import com.example.android.uamp.R; 30 | import com.example.android.uamp.utils.LogHelper; 31 | 32 | /** 33 | * Activity used to display details of the currently playing song, along with playback controls 34 | * and the playing queue. 35 | */ 36 | public class TvPlaybackActivity extends FragmentActivity { 37 | private static final String TAG = LogHelper.makeLogTag(TvPlaybackActivity.class); 38 | 39 | private MediaBrowserCompat mMediaBrowser; 40 | private TvPlaybackFragment mPlaybackFragment; 41 | 42 | @Override 43 | public void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | LogHelper.d(TAG, "Activity onCreate"); 46 | 47 | mMediaBrowser = new MediaBrowserCompat(this, 48 | new ComponentName(this, MusicService.class), 49 | mConnectionCallback, null); 50 | 51 | setContentView(R.layout.tv_playback_controls); 52 | 53 | mPlaybackFragment = (TvPlaybackFragment) getSupportFragmentManager() 54 | .findFragmentById(R.id.playback_controls_fragment); 55 | } 56 | 57 | @Override 58 | protected void onStart() { 59 | super.onStart(); 60 | LogHelper.d(TAG, "Activity onStart"); 61 | mMediaBrowser.connect(); 62 | } 63 | 64 | @Override 65 | protected void onStop() { 66 | super.onStop(); 67 | LogHelper.d(TAG, "Activity onStop"); 68 | if (getSupportMediaController() != null) { 69 | getSupportMediaController().unregisterCallback(mMediaControllerCallback); 70 | } 71 | mMediaBrowser.disconnect(); 72 | 73 | } 74 | 75 | private final MediaBrowserCompat.ConnectionCallback mConnectionCallback = 76 | new MediaBrowserCompat.ConnectionCallback() { 77 | @Override 78 | public void onConnected() { 79 | LogHelper.d(TAG, "onConnected"); 80 | try { 81 | MediaControllerCompat mediaController = new MediaControllerCompat( 82 | TvPlaybackActivity.this, mMediaBrowser.getSessionToken()); 83 | setSupportMediaController(mediaController); 84 | mediaController.registerCallback(mMediaControllerCallback); 85 | 86 | MediaMetadataCompat metadata = mediaController.getMetadata(); 87 | if (metadata != null) { 88 | mPlaybackFragment.updateMetadata(metadata); 89 | mPlaybackFragment.updatePlaybackState(mediaController.getPlaybackState()); 90 | } 91 | } catch (RemoteException e) { 92 | LogHelper.e(TAG, e, "could not connect media controller"); 93 | } 94 | } 95 | 96 | @Override 97 | public void onConnectionFailed() { 98 | LogHelper.d(TAG, "onConnectionFailed"); 99 | } 100 | 101 | @Override 102 | public void onConnectionSuspended() { 103 | LogHelper.d(TAG, "onConnectionSuspended"); 104 | getSupportMediaController().unregisterCallback(mMediaControllerCallback); 105 | setSupportMediaController(null); 106 | } 107 | }; 108 | 109 | /** 110 | * Receive callbacks from the MediaController. Here we update our state such as which queue 111 | * is being shown, the current title and description and the PlaybackState. 112 | */ 113 | private final MediaControllerCompat.Callback mMediaControllerCallback = 114 | new MediaControllerCompat.Callback() { 115 | @Override 116 | public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) { 117 | LogHelper.d(TAG, "onPlaybackStateChanged, state=", state); 118 | if (mPlaybackFragment == null || state.getState() == PlaybackStateCompat.STATE_BUFFERING) { 119 | return; 120 | } 121 | mPlaybackFragment.updatePlaybackState(state); 122 | } 123 | 124 | @Override 125 | public void onMetadataChanged(MediaMetadataCompat metadata) { 126 | LogHelper.d(TAG, "onMetadataChanged, title=", metadata.getDescription().getTitle()); 127 | if (mPlaybackFragment == null) { 128 | return; 129 | } 130 | mPlaybackFragment.updateMetadata(metadata); 131 | } 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/utils/MediaIDHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.utils; 18 | 19 | import android.support.annotation.NonNull; 20 | 21 | import java.util.Arrays; 22 | 23 | /** 24 | * Utility class to help on queue related tasks. 25 | */ 26 | public class MediaIDHelper { 27 | 28 | // Media IDs used on browseable items of MediaBrowser 29 | public static final String MEDIA_ID_ROOT = "__ROOT__"; 30 | public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__"; 31 | public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; 32 | 33 | private static final char CATEGORY_SEPARATOR = '/'; 34 | private static final char LEAF_SEPARATOR = '|'; 35 | 36 | /** 37 | * Create a String value that represents a playable or a browsable media. 38 | * 39 | * Encode the media browseable categories, if any, and the unique music ID, if any, 40 | * into a single String mediaID. 41 | * 42 | * MediaIDs are of the form /|, to make it easy 43 | * to find the category (like genre) that a music was selected from, so we 44 | * can correctly build the playing queue. This is specially useful when 45 | * one music can appear in more than one list, like "by genre -> genre_1" 46 | * and "by artist -> artist_1". 47 | 48 | * @param musicID Unique music ID for playable items, or null for browseable items. 49 | * @param categories hierarchy of categories representing this item's browsing parents 50 | * @return a hierarchy-aware media ID 51 | */ 52 | public static String createMediaID(String musicID, String... categories) { 53 | StringBuilder sb = new StringBuilder(); 54 | if (categories != null) { 55 | for (int i=0; i < categories.length; i++) { 56 | if (!isValidCategory(categories[i])) { 57 | throw new IllegalArgumentException("Invalid category: " + categories[0]); 58 | } 59 | sb.append(categories[i]); 60 | if (i < categories.length - 1) { 61 | sb.append(CATEGORY_SEPARATOR); 62 | } 63 | } 64 | } 65 | if (musicID != null) { 66 | sb.append(LEAF_SEPARATOR).append(musicID); 67 | } 68 | return sb.toString(); 69 | } 70 | 71 | private static boolean isValidCategory(String category) { 72 | return category == null || 73 | ( 74 | category.indexOf(CATEGORY_SEPARATOR) < 0 && 75 | category.indexOf(LEAF_SEPARATOR) < 0 76 | ); 77 | } 78 | 79 | /** 80 | * Extracts unique musicID from the mediaID. mediaID is, by this sample's convention, a 81 | * concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and unique 82 | * musicID. This is necessary so we know where the user selected the music from, when the music 83 | * exists in more than one music list, and thus we are able to correctly build the playing queue. 84 | * 85 | * @param mediaID that contains the musicID 86 | * @return musicID 87 | */ 88 | public static String extractMusicIDFromMediaID(@NonNull String mediaID) { 89 | int pos = mediaID.indexOf(LEAF_SEPARATOR); 90 | if (pos >= 0) { 91 | return mediaID.substring(pos+1); 92 | } 93 | return null; 94 | } 95 | 96 | /** 97 | * Extracts category and categoryValue from the mediaID. mediaID is, by this sample's 98 | * convention, a concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and 99 | * mediaID. This is necessary so we know where the user selected the music from, when the music 100 | * exists in more than one music list, and thus we are able to correctly build the playing queue. 101 | * 102 | * @param mediaID that contains a category and categoryValue. 103 | */ 104 | public static @NonNull String[] getHierarchy(@NonNull String mediaID) { 105 | int pos = mediaID.indexOf(LEAF_SEPARATOR); 106 | if (pos >= 0) { 107 | mediaID = mediaID.substring(0, pos); 108 | } 109 | return mediaID.split(String.valueOf(CATEGORY_SEPARATOR)); 110 | } 111 | 112 | public static String extractBrowseCategoryValueFromMediaID(@NonNull String mediaID) { 113 | String[] hierarchy = getHierarchy(mediaID); 114 | if (hierarchy.length == 2) { 115 | return hierarchy[1]; 116 | } 117 | return null; 118 | } 119 | 120 | public static boolean isBrowseable(@NonNull String mediaID) { 121 | return mediaID.indexOf(LEAF_SEPARATOR) < 0; 122 | } 123 | 124 | public static String getParentMediaID(@NonNull String mediaID) { 125 | String[] hierarchy = getHierarchy(mediaID); 126 | if (!isBrowseable(mediaID)) { 127 | return createMediaID(null, hierarchy); 128 | } 129 | if (hierarchy.length <= 1) { 130 | return MEDIA_ID_ROOT; 131 | } 132 | String[] parentHierarchy = Arrays.copyOf(hierarchy, hierarchy.length-1); 133 | return createMediaID(null, parentHierarchy); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/AlbumArtCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp; 18 | 19 | import android.graphics.Bitmap; 20 | import android.os.AsyncTask; 21 | import android.util.LruCache; 22 | 23 | import com.example.android.uamp.utils.BitmapHelper; 24 | import com.example.android.uamp.utils.LogHelper; 25 | 26 | import java.io.IOException; 27 | 28 | /** 29 | * Implements a basic cache of album arts, with async loading support. 30 | */ 31 | public final class AlbumArtCache { 32 | private static final String TAG = LogHelper.makeLogTag(AlbumArtCache.class); 33 | 34 | private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024; // 12 MB 35 | private static final int MAX_ART_WIDTH = 800; // pixels 36 | private static final int MAX_ART_HEIGHT = 480; // pixels 37 | 38 | // Resolution reasonable for carrying around as an icon (generally in 39 | // MediaDescription.getIconBitmap). This should not be bigger than necessary, because 40 | // the MediaDescription object should be lightweight. If you set it too high and try to 41 | // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors. 42 | private static final int MAX_ART_WIDTH_ICON = 128; // pixels 43 | private static final int MAX_ART_HEIGHT_ICON = 128; // pixels 44 | 45 | private static final int BIG_BITMAP_INDEX = 0; 46 | private static final int ICON_BITMAP_INDEX = 1; 47 | 48 | private final LruCache mCache; 49 | 50 | private static final AlbumArtCache sInstance = new AlbumArtCache(); 51 | 52 | public static AlbumArtCache getInstance() { 53 | return sInstance; 54 | } 55 | 56 | private AlbumArtCache() { 57 | // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and 58 | // Integer.MAX_VALUE: 59 | int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE, 60 | (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()/4))); 61 | mCache = new LruCache(maxSize) { 62 | @Override 63 | protected int sizeOf(String key, Bitmap[] value) { 64 | return value[BIG_BITMAP_INDEX].getByteCount() 65 | + value[ICON_BITMAP_INDEX].getByteCount(); 66 | } 67 | }; 68 | } 69 | 70 | public Bitmap getBigImage(String artUrl) { 71 | Bitmap[] result = mCache.get(artUrl); 72 | return result == null ? null : result[BIG_BITMAP_INDEX]; 73 | } 74 | 75 | public Bitmap getIconImage(String artUrl) { 76 | Bitmap[] result = mCache.get(artUrl); 77 | return result == null ? null : result[ICON_BITMAP_INDEX]; 78 | } 79 | 80 | public void fetch(final String artUrl, final FetchListener listener) { 81 | // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests 82 | // are not handled properly: they may cause redundant costly operations, like HTTP 83 | // requests and bitmap rescales. For production-level apps, we recommend you use 84 | // a proper image loading library, like Glide. 85 | Bitmap[] bitmap = mCache.get(artUrl); 86 | if (bitmap != null) { 87 | LogHelper.d(TAG, "getOrFetch: album art is in cache, using it", artUrl); 88 | listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]); 89 | return; 90 | } 91 | LogHelper.d(TAG, "getOrFetch: starting asynctask to fetch ", artUrl); 92 | 93 | new AsyncTask() { 94 | @Override 95 | protected Bitmap[] doInBackground(Void[] objects) { 96 | Bitmap[] bitmaps; 97 | try { 98 | Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap(artUrl, 99 | MAX_ART_WIDTH, MAX_ART_HEIGHT); 100 | Bitmap icon = BitmapHelper.scaleBitmap(bitmap, 101 | MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON); 102 | bitmaps = new Bitmap[] {bitmap, icon}; 103 | mCache.put(artUrl, bitmaps); 104 | } catch (IOException e) { 105 | return null; 106 | } 107 | LogHelper.d(TAG, "doInBackground: putting bitmap in cache. cache size=" + 108 | mCache.size()); 109 | return bitmaps; 110 | } 111 | 112 | @Override 113 | protected void onPostExecute(Bitmap[] bitmaps) { 114 | if (bitmaps == null) { 115 | listener.onError(artUrl, new IllegalArgumentException("got null bitmaps")); 116 | } else { 117 | listener.onFetched(artUrl, 118 | bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]); 119 | } 120 | } 121 | }.execute(); 122 | } 123 | 124 | public static abstract class FetchListener { 125 | public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage); 126 | public void onError(String artUrl, Exception e) { 127 | LogHelper.e(TAG, e, "AlbumArtFetchListener: error while downloading " + artUrl); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/example/android/uamp/model/RemoteJSONSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.android.uamp.model; 18 | 19 | import android.support.v4.media.MediaMetadataCompat; 20 | 21 | import com.example.android.uamp.utils.LogHelper; 22 | 23 | import org.json.JSONArray; 24 | import org.json.JSONException; 25 | import org.json.JSONObject; 26 | 27 | import java.io.BufferedReader; 28 | import java.io.IOException; 29 | import java.io.InputStreamReader; 30 | import java.net.URL; 31 | import java.net.URLConnection; 32 | import java.util.ArrayList; 33 | import java.util.Iterator; 34 | 35 | /** 36 | * Utility class to get a list of MusicTrack's based on a server-side JSON 37 | * configuration. 38 | */ 39 | public class RemoteJSONSource implements MusicProviderSource { 40 | 41 | private static final String TAG = LogHelper.makeLogTag(RemoteJSONSource.class); 42 | 43 | protected static final String CATALOG_URL = 44 | "http://storage.googleapis.com/automotive-media/music.json"; 45 | 46 | private static final String JSON_MUSIC = "music"; 47 | private static final String JSON_TITLE = "title"; 48 | private static final String JSON_ALBUM = "album"; 49 | private static final String JSON_ARTIST = "artist"; 50 | private static final String JSON_GENRE = "genre"; 51 | private static final String JSON_SOURCE = "source"; 52 | private static final String JSON_IMAGE = "image"; 53 | private static final String JSON_TRACK_NUMBER = "trackNumber"; 54 | private static final String JSON_TOTAL_TRACK_COUNT = "totalTrackCount"; 55 | private static final String JSON_DURATION = "duration"; 56 | 57 | @Override 58 | public Iterator iterator() { 59 | try { 60 | int slashPos = CATALOG_URL.lastIndexOf('/'); 61 | String path = CATALOG_URL.substring(0, slashPos + 1); 62 | JSONObject jsonObj = fetchJSONFromUrl(CATALOG_URL); 63 | ArrayList tracks = new ArrayList<>(); 64 | if (jsonObj != null) { 65 | JSONArray jsonTracks = jsonObj.getJSONArray(JSON_MUSIC); 66 | 67 | if (jsonTracks != null) { 68 | for (int j = 0; j < jsonTracks.length(); j++) { 69 | tracks.add(buildFromJSON(jsonTracks.getJSONObject(j), path)); 70 | } 71 | } 72 | } 73 | return tracks.iterator(); 74 | } catch (JSONException e) { 75 | LogHelper.e(TAG, e, "Could not retrieve music list"); 76 | throw new RuntimeException("Could not retrieve music list", e); 77 | } 78 | } 79 | 80 | private MediaMetadataCompat buildFromJSON(JSONObject json, String basePath) throws JSONException { 81 | String title = json.getString(JSON_TITLE); 82 | String album = json.getString(JSON_ALBUM); 83 | String artist = json.getString(JSON_ARTIST); 84 | String genre = json.getString(JSON_GENRE); 85 | String source = json.getString(JSON_SOURCE); 86 | String iconUrl = json.getString(JSON_IMAGE); 87 | int trackNumber = json.getInt(JSON_TRACK_NUMBER); 88 | int totalTrackCount = json.getInt(JSON_TOTAL_TRACK_COUNT); 89 | int duration = json.getInt(JSON_DURATION) * 1000; // ms 90 | 91 | LogHelper.d(TAG, "Found music track: ", json); 92 | 93 | // Media is stored relative to JSON file 94 | if (!source.startsWith("http")) { 95 | source = basePath + source; 96 | } 97 | if (!iconUrl.startsWith("http")) { 98 | iconUrl = basePath + iconUrl; 99 | } 100 | // Since we don't have a unique ID in the server, we fake one using the hashcode of 101 | // the music source. In a real world app, this could come from the server. 102 | String id = String.valueOf(source.hashCode()); 103 | 104 | // Adding the music source to the MediaMetadata (and consequently using it in the 105 | // mediaSession.setMetadata) is not a good idea for a real world music app, because 106 | // the session metadata can be accessed by notification listeners. This is done in this 107 | // sample for convenience only. 108 | //noinspection ResourceType 109 | return new MediaMetadataCompat.Builder() 110 | .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id) 111 | .putString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE, source) 112 | .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album) 113 | .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) 114 | .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration) 115 | .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre) 116 | .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl) 117 | .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) 118 | .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber) 119 | .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount) 120 | .build(); 121 | } 122 | 123 | /** 124 | * Download a JSON file from a server, parse the content and return the JSON 125 | * object. 126 | * 127 | * @return result JSONObject containing the parsed representation. 128 | */ 129 | private JSONObject fetchJSONFromUrl(String urlString) throws JSONException { 130 | BufferedReader reader = null; 131 | try { 132 | URLConnection urlConnection = new URL(urlString).openConnection(); 133 | reader = new BufferedReader(new InputStreamReader( 134 | urlConnection.getInputStream(), "iso-8859-1")); 135 | StringBuilder sb = new StringBuilder(); 136 | String line; 137 | while ((line = reader.readLine()) != null) { 138 | sb.append(line); 139 | } 140 | return new JSONObject(sb.toString()); 141 | } catch (JSONException e) { 142 | throw e; 143 | } catch (Exception e) { 144 | LogHelper.e(TAG, "Failed to parse the json for media list", e); 145 | return null; 146 | } finally { 147 | if (reader != null) { 148 | try { 149 | reader.close(); 150 | } catch (IOException e) { 151 | // ignore 152 | } 153 | } 154 | } 155 | } 156 | } 157 | --------------------------------------------------------------------------------