diff options
author | Xin Li <delphij@google.com> | 2022-03-08 00:18:46 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-03-08 00:18:46 +0000 |
commit | ba1e50793ff86d4ae7a0f0f917f9080627811001 (patch) | |
tree | 79de42416ce3345daafa1ca9629d4ba4ba001a2c | |
parent | 33b71ac39b2ee63b75916eed8a7d8ddd7e2fbae3 (diff) | |
parent | 141b83d1a0e025e08982c94d708dddb0c3ec9f6c (diff) | |
download | Media-ba1e50793ff86d4ae7a0f0f917f9080627811001.tar.gz |
Merge Android 12L
Bug: 222710654
Merged-In: I047833628888634e24ac2738a459ad00aea75754
Change-Id: I38929d8dcddbc5069653a263c333b787d501fdbb
29 files changed, 467 insertions, 246 deletions
@@ -28,10 +28,14 @@ android_app { required: ["allowed_privapp_com.android.car.media"], certificate: "platform", + // Keep this commented out until enough apps stop requiring a system signature. + // certificate: ":com-android-car-apps-test", privileged: true, libs: ["android.car-system-stubs"], + min_sdk_version: "30", // Media requires apis that became public in R. + target_sdk_version: "31", sdk_version: "system_current", optimize: { diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4d823c4..0cb777d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,7 +16,8 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.car.media"> + package="com.android.car.media" + android:versionCode="10000" android:versionName="1.0.0"> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> <uses-permission android:name="android.permission.INTERNET" /> @@ -41,8 +42,10 @@ android:name="android.car.application" android:resource="@xml/automotive_app_desc" /> + <!-- Private implementation, should never be exported. --> <activity android:name=".MediaActivity" + android:exported="false" android:resizeableActivity="true" android:windowSoftInputMode="stateHidden|adjustPan" android:launchMode="singleTop"> diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index e91b525..e60137c 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,7 +1,7 @@ [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES} -overlayable_resource_hook = ${REPO_ROOT}/packages/apps/Car/tests/tools/rro/verify-overlayable.py -r res -e res/values/overlayable.xml res/xml/automotive_app_desc.xml -o res/values/overlayable.xml +overlayable_resource_hook = ${REPO_ROOT}/packages/apps/Car/tests/tools/rro/verify-overlayable.py -r res -e res/values/overlayable.xml res/xml/automotive_app_desc.xml res/values/colors.xml res/values/dimens.xml res/color/progress_bar_thumb_inner_ring_color.xml res/color/progress_bar_thumb_outer_ring_color.xml -o res/values/overlayable.xml [Builtin Hooks] commit_msg_changeid_field = true diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..98b389f --- /dev/null +++ b/build.gradle @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.application' + +android { + compileSdkVersion gradle.ext.aaosLatestSDK + defaultConfig { + applicationId "com.android.car.media" + minSdkVersion 30 // Media requires apis that became public in R. + targetSdkVersion gradle.ext.aaosTargetSDK + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { + abortOnError false + } + buildTypes { + release { + minifyEnabled false + } + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } + + signingConfigs { + debug { + storeFile file('../libs/certs/com_android_car_apps_test.jks') + storePassword 'carapps' + keyAlias 'carapps' + keyPassword 'carapps' + } + } +} + +dependencies { + implementation "androidx.preference:preference:1.1.1" + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + + def lifecycle_version = "2.2.0" + implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + // Not available in 2.3+ + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + + implementation files(gradle.ext.lib_car_system_stubs) + + implementation "androidx.media:media:1.4.1" + + implementation project(":car-ui-lib") + implementation project(":car-apps-common") + implementation project(":car-media-common") + implementation project(":car-uxr-client-lib") +} diff --git a/res/layout/media_browse_grid_icons_item.xml b/res/layout/media_browse_grid_icons_item.xml index 6e0887a..7a413d8 100644 --- a/res/layout/media_browse_grid_icons_item.xml +++ b/res/layout/media_browse_grid_icons_item.xml @@ -37,6 +37,7 @@ android:id="@+id/thumbnail" android:layout_width="@dimen/media_browse_grid_icons_item_art_size" android:layout_height="@dimen/media_browse_grid_icons_item_art_size" + style="@style/MediaIconContainerStyle" android:scaleType="centerCrop"/> </FrameLayout> diff --git a/res/layout/media_browse_grid_item.xml b/res/layout/media_browse_grid_item.xml index 229a674..002b076 100644 --- a/res/layout/media_browse_grid_item.xml +++ b/res/layout/media_browse_grid_item.xml @@ -19,6 +19,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_margin="@dimen/grid_item_spacing" android:padding="@dimen/media_browse_grid_item_padding" android:layout_marginBottom="@dimen/media_browse_grid_item_margin_bottom"> @@ -38,6 +39,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:scaleType="centerCrop" + style="@style/MediaIconContainerStyle" app:aspect_ratio="1"/> </FrameLayout> diff --git a/res/layout/media_browse_list_icons_item.xml b/res/layout/media_browse_list_icons_item.xml index 9c8d972..a507f99 100644 --- a/res/layout/media_browse_list_icons_item.xml +++ b/res/layout/media_browse_list_icons_item.xml @@ -30,6 +30,7 @@ android:scaleType="centerCrop" android:layout_marginBottom="@dimen/media_browse_list_item_thumbnail_margin_bottom" android:layout_marginStart="@dimen/media_browse_list_icons_item_art_margin_start" + style="@style/MediaIconContainerStyle" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/res/layout/media_browse_list_item.xml b/res/layout/media_browse_list_item.xml index 52c2d50..f0f21dc 100644 --- a/res/layout/media_browse_list_item.xml +++ b/res/layout/media_browse_list_item.xml @@ -28,8 +28,9 @@ android:layout_width="0dp" android:layout_height="match_parent" android:scaleType="centerCrop" - app:layout_constraintStart_toStartOf="parent" android:layout_marginBottom="@dimen/media_browse_list_item_thumbnail_margin_bottom" + style="@style/MediaIconContainerStyle" + app:layout_constraintStart_toStartOf="parent" app:aspect_ratio="1"/> <!-- This guideline is necessary because there are icons preceding the text which typically have diff --git a/res/layout/queue_list_item.xml b/res/layout/queue_list_item.xml index a77187c..718e0b0 100644 --- a/res/layout/queue_list_item.xml +++ b/res/layout/queue_list_item.xml @@ -32,7 +32,8 @@ android:id="@+id/thumbnail" android:layout_width="@dimen/queue_list_item_thumbnail_size" android:layout_height="@dimen/queue_list_item_thumbnail_size" - android:scaleType="centerCrop"/> + android:scaleType="centerCrop" + style="@style/MediaIconContainerStyle"/> </FrameLayout> <Space diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 2d73a56..4ed151b 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -25,7 +25,7 @@ <string name="media_browse_more" msgid="6330295386693311592">"Daha çox…"</string> <string name="media_app_title" msgid="94717597743776797">"Media"</string> <string name="search_hint" msgid="5401750426238148416">"Mahnı, ifaçı və s. axtarın"</string> - <string name="fragment_playback_title" msgid="5014481549024607614">"İndi oxudulur"</string> + <string name="fragment_playback_title" msgid="5014481549024607614">"İndi Efirdə"</string> <string name="service_notification_title" msgid="8085444675783592744">"Mediaya qoşulur"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"Səs ayarları"</string> <string name="menu_item_app_selector_title" msgid="4587248991114338595">"Tətbiqləri dəyişdirin"</string> diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 79bc4ea..aca4209 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -21,7 +21,7 @@ <string name="nothing_to_play" msgid="594633010485167765">"Hemen ez dago araka daitekeen multimedia-edukirik"</string> <string name="cannot_connect_to_app" msgid="4732888036680095414">"Une honetan, <xliff:g id="ID_1">%s</xliff:g> aplikazioak ez du funtzionatzen."</string> <string name="unknown_media_provider_name" msgid="4238216994694326667">"Ezezaguna"</string> - <string name="unknown_error" msgid="6146463797752964372">"Arazo bat izan da"</string> + <string name="unknown_error" msgid="6146463797752964372">"Arazoren bat izan da"</string> <string name="media_browse_more" msgid="6330295386693311592">"Gehiago…"</string> <string name="media_app_title" msgid="94717597743776797">"Multimedia-edukia"</string> <string name="search_hint" msgid="5401750426238148416">"Bilatu abestiak, artistak eta beste…"</string> diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index 835e9a1..80105f6 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -24,7 +24,7 @@ <string name="unknown_error" msgid="6146463797752964372">"ਕੋਈ ਗੜਬੜ ਹੋਈ"</string> <string name="media_browse_more" msgid="6330295386693311592">"ਹੋਰ…"</string> <string name="media_app_title" msgid="94717597743776797">"ਮੀਡੀਆ"</string> - <string name="search_hint" msgid="5401750426238148416">"ਗੀਤ, ਕਲਾਕਾਰ ਤੇ ਹੋਰ ਖੋਜੋ"</string> + <string name="search_hint" msgid="5401750426238148416">"ਗੀਤ, ਕਲਾਕਾਰ ਖੋਜੋ, ਹੋਰ"</string> <string name="fragment_playback_title" msgid="5014481549024607614">"ਹੁਣੇ ਚੱਲ ਰਿਹਾ ਹੈ"</string> <string name="service_notification_title" msgid="8085444675783592744">"ਮੀਡੀਆ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"ਧੁਨੀ ਸੈਟਿੰਗਾਂ"</string> diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 6297b12..14cd0f4 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -24,7 +24,7 @@ <string name="unknown_error" msgid="6146463797752964372">"ఏదో తప్పు జరిగింది"</string> <string name="media_browse_more" msgid="6330295386693311592">"మరిన్ని…"</string> <string name="media_app_title" msgid="94717597743776797">"మీడియా"</string> - <string name="search_hint" msgid="5401750426238148416">"పాటలు, ఆర్టిస్ట్లు మొ. వెతకండి..."</string> + <string name="search_hint" msgid="5401750426238148416">"పాటలు, కళాకారులు, మరిన్నింటిని వెతకండి..."</string> <string name="fragment_playback_title" msgid="5014481549024607614">"ప్రస్తుతం ప్లే అవుతున్నవి"</string> <string name="service_notification_title" msgid="8085444675783592744">"మీడియాకు కనెక్ట్ చేస్తోంది"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"ధ్వని సెట్టింగ్లు"</string> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index b928667..27f7af2 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -25,7 +25,7 @@ <string name="media_browse_more" msgid="6330295386693311592">"更多…"</string> <string name="media_app_title" msgid="94717597743776797">"媒体"</string> <string name="search_hint" msgid="5401750426238148416">"搜索歌曲、音乐人等…"</string> - <string name="fragment_playback_title" msgid="5014481549024607614">"正在播放"</string> + <string name="fragment_playback_title" msgid="5014481549024607614">"现正播放"</string> <string name="service_notification_title" msgid="8085444675783592744">"正在连接到媒体"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"声音设置"</string> <string name="menu_item_app_selector_title" msgid="4587248991114338595">"切换应用"</string> diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 15e6544..6254b03 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -25,7 +25,7 @@ <string name="media_browse_more" msgid="6330295386693311592">"更多…"</string> <string name="media_app_title" msgid="94717597743776797">"媒體"</string> <string name="search_hint" msgid="5401750426238148416">"搜尋歌曲、演出者和更多內容…"</string> - <string name="fragment_playback_title" msgid="5014481549024607614">"正在播放"</string> + <string name="fragment_playback_title" msgid="5014481549024607614">"現正播放"</string> <string name="service_notification_title" msgid="8085444675783592744">"正在連接媒體"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"音效設定"</string> <string name="menu_item_app_selector_title" msgid="4587248991114338595">"切換應用程式"</string> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index a909a72..5b54540 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -25,7 +25,7 @@ <string name="media_browse_more" msgid="6330295386693311592">"更多…"</string> <string name="media_app_title" msgid="94717597743776797">"媒體"</string> <string name="search_hint" msgid="5401750426238148416">"搜尋歌曲、演出者和更多內容…"</string> - <string name="fragment_playback_title" msgid="5014481549024607614">"正在播放"</string> + <string name="fragment_playback_title" msgid="5014481549024607614">"現正播放"</string> <string name="service_notification_title" msgid="8085444675783592744">"正在連線到媒體"</string> <string name="menu_item_sound_settings_title" msgid="58887078120809669">"音效設定"</string> <string name="menu_item_app_selector_title" msgid="4587248991114338595">"切換應用程式"</string> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 84d326e..ebc0b8c 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -97,7 +97,7 @@ <!-- BrowseFragment.java --> <!-- Spacer used between the app bar and the top of the browse list/grid --> <dimen name="browse_spacer_height">@dimen/car_ui_padding_2</dimen> - <dimen name="grid_item_spacing">@dimen/car_ui_padding_3</dimen> + <dimen name="grid_item_spacing">@dimen/car_ui_padding_1</dimen> <!-- Space between title and subtitle on media browse list/grid --> <dimen name="media_browse_subtitle_margin_top">@dimen/car_ui_padding_0</dimen> diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml index b7206d0..d02874e 100644 --- a/res/values/overlayable.xml +++ b/res/values/overlayable.xml @@ -33,108 +33,17 @@ REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py <item type="bool" name="show_time_for_now_playing_queue_list_item"/> <item type="bool" name="switch_to_playback_view_when_playable_item_is_clicked"/> <item type="bool" name="use_media_source_color_for_progress_bar"/> - <item type="color" name="album_art_background"/> - <item type="color" name="appbar_view_icon_tint"/> - <item type="color" name="appbar_view_settings_tint"/> - <item type="color" name="browse_playback_bg_color"/> - <item type="color" name="no_content_text_color"/> - <item type="color" name="progress_bar_background"/> - <item type="color" name="progress_bar_highlight"/> - <item type="color" name="progress_bar_thumb_color"/> - <item type="color" name="progress_bar_thumb_inner_ring_color"/> - <item type="color" name="progress_bar_thumb_outer_ring_color"/> - <item type="color" name="queue_playing_icon_color"/> - <item type="color" name="search_bar_underline_color"/> - <item type="color" name="search_hint_text_color"/> - <item type="dimen" name="appbar_view_control_buttons_margin_end"/> - <item type="dimen" name="appbar_view_control_buttons_spacing"/> - <item type="dimen" name="appbar_view_nav_button_width"/> - <item type="dimen" name="appbar_view_tabs_margin_end"/> - <item type="dimen" name="appbar_view_tabs_margin_start"/> - <item type="dimen" name="appbar_view_title_margin_start"/> - <item type="dimen" name="apps_max_content_width"/> - <item type="dimen" name="art_metadata_margin"/> <item type="dimen" name="browse_fragment_bottom_padding"/> <item type="dimen" name="browse_fragment_top_padding"/> <item type="dimen" name="browse_fragment_top_padding_stacked"/> - <item type="dimen" name="browse_playback_controls_height"/> - <item type="dimen" name="browse_spacer_height"/> - <item type="dimen" name="browse_state_error_margin_top"/> - <item type="dimen" name="browse_tab_alpha_selected"/> - <item type="dimen" name="browse_tab_alpha_unselected"/> - <item type="dimen" name="browse_tab_padding"/> - <item type="dimen" name="browse_tab_width"/> - <item type="dimen" name="controls_margin"/> - <item type="dimen" name="controls_spacing_inner"/> - <item type="dimen" name="controls_spacing_outer"/> - <item type="dimen" name="controls_tap_target_height"/> - <item type="dimen" name="controls_tap_target_width"/> - <item type="dimen" name="fragment_error_button_margin_bottom"/> - <item type="dimen" name="fragment_error_button_margin_top"/> - <item type="dimen" name="fragment_error_message_margin_x"/> - <item type="dimen" name="fragment_metadata_margin_x"/> - <item type="dimen" name="fragment_playback_queue_overlap_bottom"/> - <item type="dimen" name="fragment_playback_queue_overlap_top"/> - <item type="dimen" name="grid_item_spacing"/> - <item type="dimen" name="media_activity_close_vector_x"/> - <item type="dimen" name="media_activity_close_vector_y"/> - <item type="dimen" name="media_activity_controls_container_padding"/> - <item type="dimen" name="media_activity_controls_margin_start"/> <item type="dimen" name="media_background_alpha"/> - <item type="dimen" name="media_browse_grid_icons_item_art_size"/> - <item type="dimen" name="media_browse_grid_item_margin_bottom"/> - <item type="dimen" name="media_browse_grid_item_padding"/> - <item type="dimen" name="media_browse_grid_item_text_margin_top"/> - <item type="dimen" name="media_browse_header_item_height"/> - <item type="dimen" name="media_browse_header_item_margin_x"/> - <item type="dimen" name="media_browse_indicator_size"/> <item type="dimen" name="media_browse_list_icons_item_art_margin_start"/> - <item type="dimen" name="media_browse_list_icons_item_art_size"/> <item type="dimen" name="media_browse_list_icons_item_text_margin_x"/> - <item type="dimen" name="media_browse_list_item_arrow_size"/> - <item type="dimen" name="media_browse_list_item_height"/> - <item type="dimen" name="media_browse_list_item_icon_margin_start"/> <item type="dimen" name="media_browse_list_item_text_margin_x"/> - <item type="dimen" name="media_browse_list_item_thumbnail_margin_bottom"/> - <item type="dimen" name="media_browse_list_item_thumbnail_size"/> - <item type="dimen" name="media_browse_subtitle_margin_top"/> - <item type="dimen" name="media_scrim_alpha"/> - <item type="dimen" name="media_scrim_darkened_alpha"/> - <item type="dimen" name="metadata_subtitles_margin"/> - <item type="dimen" name="metadata_title_subtitle_margin"/> <item type="dimen" name="minimized_control_bar_margin_bottom"/> - <item type="dimen" name="missing_permission_icon_size"/> - <item type="dimen" name="music_action_icon_inset"/> - <item type="dimen" name="music_action_ripple_inset"/> - <item type="dimen" name="playback_album_art_size"/> - <item type="dimen" name="playback_background_blur_radius"/> - <item type="dimen" name="playback_background_blur_scale"/> - <item type="dimen" name="playback_background_raw_image_size"/> - <item type="dimen" name="playback_queue_background_alpha"/> - <item type="dimen" name="playback_queue_button_margin_end"/> - <item type="dimen" name="playback_queue_list_padding_top"/> - <item type="dimen" name="playback_seekbar_height"/> <item type="dimen" name="playback_seekbar_margin_x"/> - <item type="dimen" name="playback_seekbar_padding_x"/> - <item type="dimen" name="playback_seekbar_thumb_height"/> - <item type="dimen" name="playback_seekbar_thumb_inner_ring_inner_radius"/> - <item type="dimen" name="playback_seekbar_thumb_inner_ring_thickness"/> - <item type="dimen" name="playback_seekbar_thumb_offset"/> - <item type="dimen" name="playback_seekbar_thumb_outer_ring_inner_radius"/> - <item type="dimen" name="playback_seekbar_thumb_outer_ring_thickness"/> - <item type="dimen" name="playback_seekbar_thumb_width"/> - <item type="dimen" name="playback_seekbar_track_height"/> - <item type="dimen" name="playback_title_margin_end"/> - <item type="dimen" name="queue_button_background_size"/> - <item type="dimen" name="queue_fading_edge_length"/> - <item type="dimen" name="queue_list_item_height"/> <item type="dimen" name="queue_list_item_padding_x"/> - <item type="dimen" name="queue_list_item_spacer_width"/> <item type="dimen" name="queue_list_item_thumbnail_container_width"/> - <item type="dimen" name="queue_list_item_thumbnail_size"/> - <item type="dimen" name="queue_list_item_title_time_margin"/> - <item type="dimen" name="tab_view_icon_margin_bottom"/> - <item type="dimen" name="tab_view_icon_size"/> <item type="drawable" name="error_illustration"/> <item type="drawable" name="ic_arrow_back"/> <item type="drawable" name="ic_chevron_right"/> @@ -251,6 +160,7 @@ REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py <item type="style" name="BrowseListTitleStyle"/> <item type="style" name="BrowseSubheaderStyle"/> <item type="style" name="ErrorTextStyle"/> + <item type="style" name="MediaIconContainerStyle"/> <item type="style" name="MetadataContainerStyle"/> <item type="style" name="MetadataPlaybackSubtitleStyle"/> <item type="style" name="MetadataPlaybackTitleStyle"/> diff --git a/res/values/styles.xml b/res/values/styles.xml index 6fdceae..202177e 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -92,4 +92,6 @@ <style name="BrowseListItemRightArrowStyle"> <item name="android:src">@null</item> </style> + + <style name="MediaIconContainerStyle"/> </resources> diff --git a/res/values/themes.xml b/res/values/themes.xml index ac4d809..ce18182 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -15,7 +15,7 @@ limitations under the License. --> <resources> <!-- The theme for the Media Center application. --> - <style name="Theme.Media" parent="android:Theme.DeviceDefault.NoActionBar" > + <style name="Theme.Media" parent="Theme.CarUi" > <item name="textAppearanceGridItem">@android:style/TextAppearance.DeviceDefault.Medium</item> <item name="textAppearanceGridItemSecondary">@android:style/TextAppearance.DeviceDefault.Small</item> <item name="android:splitMotionEvents">false</item> diff --git a/src/com/android/car/media/BrowseViewController.java b/src/com/android/car/media/BrowseViewController.java index c1869e4..db1cca3 100644 --- a/src/com/android/car/media/BrowseViewController.java +++ b/src/com/android/car/media/BrowseViewController.java @@ -32,20 +32,19 @@ import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.android.car.apps.common.util.FutureData; import com.android.car.apps.common.util.ViewUtils; -import com.android.car.arch.common.FutureData; import com.android.car.media.browse.BrowseAdapter; import com.android.car.media.browse.LimitedBrowseAdapter; -import com.android.car.media.common.GridSpacingItemDecoration; import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.browse.MediaBrowserViewModelImpl; import com.android.car.media.common.browse.MediaItemsRepository.MediaItemsLiveData; import com.android.car.media.common.source.MediaSource; import com.android.car.ui.FocusArea; import com.android.car.ui.baselayout.Insets; +import com.android.car.ui.recyclerview.CarUiRecyclerView; import com.android.car.uxr.LifeCycleObserverUxrContentLimiter; import com.android.car.uxr.UxrContentLimiterImpl; @@ -69,7 +68,7 @@ public class BrowseViewController { private final boolean mDisplayMediaItems; private final LifeCycleObserverUxrContentLimiter mUxrContentLimiter; private final View mContent; - private final RecyclerView mBrowseList; + private final CarUiRecyclerView mBrowseList; private final ImageView mErrorIcon; private final TextView mMessage; private final LimitedBrowseAdapter mLimitedBrowseAdapter; @@ -184,12 +183,8 @@ public class BrowseViewController { FragmentActivity activity = callbacks.getActivity(); mViewModel = ViewModelProviders.of(activity).get(MediaActivity.ViewModel.class); - mBrowseList.addItemDecoration(new GridSpacingItemDecoration( - activity.getResources().getDimensionPixelSize(R.dimen.grid_item_spacing))); - - GridLayoutManager manager = (GridLayoutManager) mBrowseList.getLayoutManager(); BrowseAdapter browseAdapter = new BrowseAdapter(mBrowseList.getContext()); - mLimitedBrowseAdapter = new LimitedBrowseAdapter(browseAdapter, manager, + mLimitedBrowseAdapter = new LimitedBrowseAdapter(mBrowseList, browseAdapter, mBrowseAdapterObserver); mBrowseList.setAdapter(mLimitedBrowseAdapter); @@ -362,16 +357,16 @@ public class BrowseViewController { int duration = mFadeDuration; if (items == null) { mMessage.setText(getErrorMessage()); - ViewUtils.hideViewAnimated(mBrowseList, duration); + ViewUtils.hideViewAnimated(mBrowseList.getView(), duration); ViewUtils.showViewAnimated(mMessage, duration); ViewUtils.showViewAnimated(mErrorIcon, duration); } else if (items.isEmpty()) { mMessage.setText(R.string.nothing_to_play); - ViewUtils.hideViewAnimated(mBrowseList, duration); + ViewUtils.hideViewAnimated(mBrowseList.getView(), duration); ViewUtils.hideViewAnimated(mErrorIcon, duration); ViewUtils.showViewAnimated(mMessage, duration); } else { - ViewUtils.showViewAnimated(mBrowseList, duration); + ViewUtils.showViewAnimated(mBrowseList.getView(), duration); ViewUtils.hideViewAnimated(mErrorIcon, duration); ViewUtils.hideViewAnimated(mMessage, duration); } diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java index 21f6d46..b437a1a 100644 --- a/src/com/android/car/media/MediaActivity.java +++ b/src/com/android/car/media/MediaActivity.java @@ -17,8 +17,8 @@ package com.android.car.media; import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE; +import static com.android.car.apps.common.util.LiveDataFunctions.dataOf; import static com.android.car.apps.common.util.VectorMath.EPSILON; -import static com.android.car.arch.common.LiveDataFunctions.dataOf; import android.annotation.SuppressLint; import android.app.AlertDialog; @@ -27,6 +27,8 @@ import android.app.PendingIntent; import android.car.Car; import android.car.content.pm.CarPackageManager; import android.car.drivingstate.CarUxRestrictions; +import android.car.media.CarMediaIntents; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -51,15 +53,16 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModelProviders; import com.android.car.apps.common.util.CarPackageManagerUtils; +import com.android.car.apps.common.util.FutureData; import com.android.car.apps.common.util.VectorMath; import com.android.car.apps.common.util.ViewUtils; -import com.android.car.arch.common.FutureData; import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.MinimizedPlaybackControlBar; import com.android.car.media.common.PlaybackErrorsHelper; import com.android.car.media.common.browse.MediaItemsRepository; import com.android.car.media.common.playback.PlaybackViewModel; import com.android.car.media.common.source.MediaSource; +import com.android.car.media.common.source.MediaTrampolineHelper; import com.android.car.ui.AlertDialogBuilder; import com.android.car.ui.utils.CarUxRestrictionsUtil; @@ -105,17 +108,12 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont private float mCloseVectorY; private float mCloseVectorNorm; - private CarUxRestrictionsUtil mCarUxRestrictionsUtil; - private CarUxRestrictions mActiveCarUxRestrictions; - @CarUxRestrictions.CarUxRestrictionsInfo - private int mRestrictions; - private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener = - (carUxRestrictions) -> mActiveCarUxRestrictions = carUxRestrictions; - private PlaybackFragment.PlaybackFragmentListener mPlaybackFragmentListener = () -> changeMode(Mode.BROWSING); + private MediaTrampolineHelper mMediaTrampoline; + /** * Possible modes of the application UI * Todo: refactor into non exclusive flags to allow concurrent modes (eg: play details & browse) @@ -155,6 +153,8 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont localViewModel.getBrowsedMediaSource().observe(this, this::onMediaSourceChanged); + mMediaTrampoline = new MediaTrampolineHelper(this); + mPlaybackFragment = new PlaybackFragment(); mPlaybackFragment.setListener(mPlaybackFragmentListener); @@ -172,9 +172,6 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont .replace(R.id.playback_container, mPlaybackFragment) .commit(); - mMediaActivityController = new MediaActivityController(this, getMediaItemsRepository(), - mCarPackageManager, mBrowseContainer); - playbackViewModel.getPlaybackController().observe(this, playbackController -> { if (playbackController != null) playbackController.prepare(); @@ -187,23 +184,60 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont mCar = Car.createCar(this); mCarPackageManager = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE); - mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(this); - mRestrictions = CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP; - mCarUxRestrictionsUtil.register(mListener); + mMediaActivityController = new MediaActivityController(this, getMediaItemsRepository(), + mCarPackageManager, mBrowseContainer); mPlaybackContainer.setOnTouchListener(new ClosePlaybackDetector(this)); } @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); // getIntent() should always return the most recent + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onNewIntent: " + intent); + } + } + + @Override + protected void onResume() { + super.onResume(); + + Intent intent = getIntent(); + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onResume intent: " + intent); + } + + if (intent != null) { + String compName = getIntent().getStringExtra(CarMediaIntents.EXTRA_MEDIA_COMPONENT); + ComponentName launchedSourceComp = (compName == null) ? null : + ComponentName.unflattenFromString(compName); + + if (launchedSourceComp == null) { + // Might happen if there's no media source at all on the system as the + // MediaDispatcherActivity always specifies the component otherwise. + Log.w(TAG, "launchedSourceComp should almost never be null: " + compName); + } + + mMediaTrampoline.setLaunchedMediaSource(launchedSourceComp); + + // Mark the intent as consumed so that coming back from the media app selector doesn't + // set the source again. + setIntent(null); + } + } + + @Override protected void onDestroy() { - mCarUxRestrictionsUtil.unregister(mListener); mCar.disconnect(); mMediaActivityController.onDestroy(); super.onDestroy(); } private boolean isUxRestricted() { - return CarUxRestrictionsUtil.isRestricted(mRestrictions, mActiveCarUxRestrictions); + return CarUxRestrictionsUtil.isRestricted(CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP, + CarUxRestrictionsUtil.getInstance(this).getCurrentRestrictions()); } private void handlePlaybackState(PlaybackViewModel.PlaybackStateWrapper state, @@ -349,12 +383,7 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont // state that can be displayed (and some send a displayable state after sending a // non displayable one...). changeModeInternal(Mode.BROWSING, false); - - // Always go through the trampoline activity to keep the dispatching logic there. - startActivity(new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE)); } - } else { - startActivity(new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE)); } } @@ -588,6 +617,11 @@ public class MediaActivity extends FragmentActivity implements MediaActivityCont } void saveBrowsedMediaSource(MediaSource mediaSource) { + Resources res = getApplication().getResources(); + if (MediaDispatcherActivity.isCustomMediaSource(res, mediaSource)) { + Log.i(TAG, "Ignoring custom media source: " + mediaSource); + return; + } MediaSource oldSource = getMediaSourceValue(); if (Log.isLoggable(TAG, Log.INFO)) { Log.i(TAG, "MediaSource changed from " + oldSource + " to " + mediaSource); diff --git a/src/com/android/car/media/MediaActivityController.java b/src/com/android/car/media/MediaActivityController.java index 9a7d39b..a29b40d 100644 --- a/src/com/android/car/media/MediaActivityController.java +++ b/src/com/android/car/media/MediaActivityController.java @@ -37,9 +37,9 @@ import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.android.car.apps.common.util.FutureData; import com.android.car.apps.common.util.ViewUtils; import com.android.car.apps.common.util.ViewUtils.ViewAnimEndListener; -import com.android.car.arch.common.FutureData; import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.browse.MediaBrowserViewModelImpl; import com.android.car.media.common.browse.MediaItemsRepository; @@ -50,7 +50,9 @@ import com.android.car.media.widgets.AppBarController; import com.android.car.ui.FocusParkingView; import com.android.car.ui.baselayout.Insets; import com.android.car.ui.recyclerview.CarUiRecyclerView; -import com.android.car.ui.toolbar.Toolbar; +import com.android.car.ui.toolbar.NavButtonMode; +import com.android.car.ui.toolbar.SearchConfig; +import com.android.car.ui.toolbar.SearchMode; import java.util.ArrayList; import java.util.Collection; @@ -203,6 +205,10 @@ public class MediaActivityController extends ViewControllerBase { } private void onMediaBrowsingStateChanged(BrowsingState newBrowsingState) { + if (newBrowsingState == null) { + Log.e(TAG, "Null browsing state (no media source!)"); + return; + } switch (newBrowsingState.mConnectionStatus) { case CONNECTING: break; @@ -255,7 +261,7 @@ public class MediaActivityController extends ViewControllerBase { mAppBarController.setListener(mAppBarListener); mAppBarController.setSearchQuery(mViewModel.getSearchQuery()); - if (mAppBarController.canShowSearchResultsView()) { + if (mAppBarController.getSearchCapabilities().canShowSearchResultsView()) { // TODO(b/180441965) eliminate the need to create a different view and use // mSearchResultsController.getContent() instead. RecyclerView toolbarSearchResultsView = new RecyclerView(activity); @@ -268,7 +274,9 @@ public class MediaActivityController extends ViewControllerBase { toolbarSearchResultsView.setBackground( activity.getDrawable(R.drawable.car_ui_ime_wide_screen_background)); - mAppBarController.setSearchResultsView(toolbarSearchResultsView); + mAppBarController.setSearchConfig(SearchConfig.builder() + .setSearchResultsView(toolbarSearchResultsView) + .build()); } updateAppBar(); @@ -442,7 +450,12 @@ public class MediaActivityController extends ViewControllerBase { CarUiRecyclerView carUiRecyclerView = controller.getContent().findViewById(R.id.browse_list); if (carUiRecyclerView != null && carUiRecyclerView instanceof LazyLayoutView - && !carUiRecyclerView.hasFocus() && !carUiRecyclerView.isInTouchMode()) { + && !carUiRecyclerView.getView().hasFocus() + && !carUiRecyclerView.getView().isInTouchMode()) { + // Park the focus on the FocusParkingView to ensure that it can restore focus inside + // the LazyLayoutView successfully later. + mFpv.performAccessibilityAction(ACTION_FOCUS, null); + LazyLayoutView lazyLayoutView = (LazyLayoutView) carUiRecyclerView; com.android.car.ui.utils.ViewUtils.initFocus(lazyLayoutView); } @@ -452,18 +465,18 @@ public class MediaActivityController extends ViewControllerBase { @Nullable ViewAnimEndListener listener) { CarUiRecyclerView carUiRecyclerView = content.findViewById(R.id.browse_list); if (carUiRecyclerView != null && carUiRecyclerView instanceof LazyLayoutView - && !carUiRecyclerView.isInTouchMode()) { + && !carUiRecyclerView.getView().isInTouchMode()) { // If a CarUiRecyclerView is about to hide and it has focus, park the focus on the // FocusParkingView before hiding the CarUiRecyclerView. Otherwise hiding the focused // view will cause the Android framework to move focus to another view, causing visual // jank. - if (!show && carUiRecyclerView.hasFocus()) { + if (!show && carUiRecyclerView.getView().hasFocus()) { mFpv.performAccessibilityAction(ACTION_FOCUS, null); } // If a new CarUiRecyclerView is about to show and there is no view focused or the // FocusParkingView is focused, restore focus in the new CarUiRecyclerView. if (show) { - View focusedView = carUiRecyclerView.getRootView().findFocus(); + View focusedView = carUiRecyclerView.getView().getRootView().findFocus(); if (focusedView == null || focusedView instanceof FocusParkingView) { LazyLayoutView lazyLayoutView = (LazyLayoutView) carUiRecyclerView; com.android.car.ui.utils.ViewUtils.initFocus(lazyLayoutView); @@ -616,13 +629,14 @@ public class MediaActivityController extends ViewControllerBase { updateAppBar(); } - private void updateAppBarTitle() { + private CharSequence getAppBarTitle() { boolean isStacked = !isAtTopStack(); final CharSequence title; if (isStacked) { // If not at top level, show the current item as title - title = getCurrentMediaItem().getTitle(); + MediaItemMetadata item = getCurrentMediaItem(); + title = item != null ? item.getTitle() : ""; } else if (mTopItems == null) { // If still loading the tabs, force to show an empty bar. title = ""; @@ -635,7 +649,7 @@ public class MediaActivityController extends ViewControllerBase { title = getAppBarDefaultTitle(mediaSource); } - mAppBarController.setTitle(title); + return title; } /** @@ -647,9 +661,11 @@ public class MediaActivityController extends ViewControllerBase { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "App bar is in stacked state: " + isStacked); } - Toolbar.State unstackedState = isSearching ? Toolbar.State.SEARCH : Toolbar.State.HOME; - updateAppBarTitle(); - mAppBarController.setState(isStacked ? Toolbar.State.SUBPAGE : unstackedState); + + mAppBarController.setSearchMode(isSearching ? SearchMode.SEARCH : SearchMode.DISABLED); + mAppBarController.setNavButtonMode(isStacked || isSearching + ? NavButtonMode.BACK : NavButtonMode.DISABLED); + mAppBarController.setTitle(!isSearching ? getAppBarTitle() : null); mAppBarController.showSearchIfSupported(!isSearching || isStacked); } diff --git a/src/com/android/car/media/MediaDispatcherActivity.java b/src/com/android/car/media/MediaDispatcherActivity.java index 973937b..282cfa5 100644 --- a/src/com/android/car/media/MediaDispatcherActivity.java +++ b/src/com/android/car/media/MediaDispatcherActivity.java @@ -1,13 +1,17 @@ package com.android.car.media; +import static android.car.media.CarMediaIntents.EXTRA_MEDIA_COMPONENT; import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE; import android.car.Car; +import android.car.media.CarMediaIntents; import android.content.ComponentName; import android.content.Intent; +import android.content.res.Resources; import android.os.Bundle; import android.util.Log; +import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; import com.android.car.media.common.source.MediaSource; @@ -25,50 +29,60 @@ import java.util.Set; public class MediaDispatcherActivity extends FragmentActivity { private static final String TAG = "MediaDispatcherActivity"; + private static Set<String> sCustomMediaComponents = null; - private final Set<String> mCustomMediaComponents = new HashSet<>(); + static boolean isCustomMediaSource(Resources res, @Nullable MediaSource source) { + if (sCustomMediaComponents == null) { + sCustomMediaComponents = new HashSet<>(); + sCustomMediaComponents.addAll( + Arrays.asList(res.getStringArray(R.array.custom_media_packages))); + } + + return (source != null) + && sCustomMediaComponents.contains( + source.getBrowseServiceComponentName().flattenToString()); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mCustomMediaComponents.addAll( - Arrays.asList(getResources().getStringArray(R.array.custom_media_packages))); - Intent intent = getIntent(); - String action = intent != null ? intent.getAction() : null; + String action = null; + String componentName = null; + if (intent != null) { + action = intent.getAction(); + componentName = intent.getStringExtra(EXTRA_MEDIA_COMPONENT); + } - MediaSourceViewModel mediaSrcVM = MediaSourceViewModel.get(getApplication(), - MEDIA_SOURCE_MODE_BROWSE); - MediaSource mediaSrc = null; + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, "onCreate action: " + action + " component: " + componentName); + } - if (Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE.equals(action)) { - String componentName = intent.getStringExtra(Car.CAR_EXTRA_MEDIA_COMPONENT); + MediaSource mediaSrc = null; + if (CarMediaIntents.ACTION_MEDIA_TEMPLATE.equals(action)) { if (componentName != null) { - ComponentName component = ComponentName.unflattenFromString(componentName); - mediaSrc = MediaSource.create(this, component); - if (mediaSrc != null) { - mediaSrcVM.setPrimaryMediaSource(mediaSrc, MEDIA_SOURCE_MODE_BROWSE); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onCreate componentName : " + componentName); - } + ComponentName mediaSrcComp = ComponentName.unflattenFromString(componentName); + if (mediaSrcComp != null) { + mediaSrc = MediaSource.create(this, mediaSrcComp); } } } + // Retrieve the current source if none was set. However, do NOT set it and rely on setting + // the EXTRA_MEDIA_COMPONENT on the intent launched below. This avoids source notifications + // as well as extra trips back here, all of which would be useless. if (mediaSrc == null) { + MediaSourceViewModel mediaSrcVM = MediaSourceViewModel.get(getApplication(), + MEDIA_SOURCE_MODE_BROWSE); mediaSrc = mediaSrcVM.getPrimaryMediaSource().getValue(); } Intent newIntent = null; - if (mediaSrc != null - && mCustomMediaComponents.contains( - mediaSrc.getBrowseServiceComponentName().flattenToString())) { + if ((mediaSrc != null) && isCustomMediaSource(getResources(), mediaSrc)) { // Launch custom app (e.g. Radio) String srcPackage = mediaSrc.getPackageName(); newIntent = getPackageManager().getLaunchIntentForPackage(srcPackage); - newIntent.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, - mediaSrc.getBrowseServiceComponentName().flattenToString()); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Getting launch intent for package : " + srcPackage + (newIntent != null ? " succeeded" : " failed")); @@ -79,6 +93,12 @@ public class MediaDispatcherActivity extends FragmentActivity { newIntent = new Intent(this, MediaActivity.class); } + // Add the selected media source to the intent so the launched activity gets it right away + if (mediaSrc != null) { + newIntent.putExtra(EXTRA_MEDIA_COMPONENT, + mediaSrc.getBrowseServiceComponentName().flattenToString()); + } + newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(newIntent); finish(); diff --git a/src/com/android/car/media/PlaybackFragment.java b/src/com/android/car/media/PlaybackFragment.java index cbf1ade..fa3ab7a 100644 --- a/src/com/android/car/media/PlaybackFragment.java +++ b/src/com/android/car/media/PlaybackFragment.java @@ -55,10 +55,11 @@ import com.android.car.media.common.playback.PlaybackViewModel; import com.android.car.media.common.source.MediaSourceViewModel; import com.android.car.media.widgets.AppBarController; import com.android.car.ui.core.CarUi; +import com.android.car.ui.recyclerview.CarUiRecyclerView; import com.android.car.ui.recyclerview.ContentLimiting; import com.android.car.ui.recyclerview.ScrollingLimitedViewHolder; import com.android.car.ui.toolbar.MenuItem; -import com.android.car.ui.toolbar.Toolbar; +import com.android.car.ui.toolbar.NavButtonMode; import com.android.car.ui.toolbar.ToolbarController; import com.android.car.ui.utils.DirectManipulationHelper; import com.android.car.uxr.LifeCycleObserverUxrContentLimiter; @@ -86,7 +87,7 @@ public class PlaybackFragment extends Fragment { private View mControlBarScrim; private PlaybackControlsActionBar mPlaybackControls; private QueueItemsAdapter mQueueAdapter; - private RecyclerView mQueue; + private CarUiRecyclerView mQueue; private ViewGroup mSeekBarContainer; private SeekBar mSeekBar; private List<View> mViewsToHideForCustomActions; @@ -483,8 +484,7 @@ public class PlaybackFragment extends Fragment { mAppBarController.setTitle(R.string.fragment_playback_title); mAppBarController.setBackgroundShown(false); - mAppBarController.setNavButtonMode(Toolbar.NavButtonMode.DOWN); - mAppBarController.setState(Toolbar.State.SUBPAGE); + mAppBarController.setNavButtonMode(NavButtonMode.DOWN); // Update toolbar's logo MediaSourceViewModel mediaSourceViewModel = getMediaSourceViewModel(); @@ -623,6 +623,8 @@ public class PlaybackFragment extends Fragment { int decorationHeight = getResources().getDimensionPixelSize( R.dimen.playback_queue_list_padding_top); + // TODO (b/206038962): addItemDecoration is not supported anymore. Find another way to + // support this. // Put the decoration above the first item. int decorationPosition = 0; mQueue.addItemDecoration(new QueueTopItemDecoration(decorationHeight, decorationPosition)); diff --git a/src/com/android/car/media/browse/LimitedBrowseAdapter.java b/src/com/android/car/media/browse/LimitedBrowseAdapter.java index 6d3c6a7..ee0a7f7 100644 --- a/src/com/android/car/media/browse/LimitedBrowseAdapter.java +++ b/src/com/android/car/media/browse/LimitedBrowseAdapter.java @@ -22,6 +22,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.car.media.R; import com.android.car.media.common.MediaItemMetadata; +import com.android.car.ui.recyclerview.CarUiGridLayoutStyle; +import com.android.car.ui.recyclerview.CarUiRecyclerView; import com.android.car.ui.recyclerview.DelegatingContentLimitingAdapter; import java.util.List; @@ -31,21 +33,23 @@ import java.util.List; */ public class LimitedBrowseAdapter extends DelegatingContentLimitingAdapter<BrowseViewHolder> { + private final CarUiRecyclerView mRecyclerView; private final BrowseAdapter mBrowseAdapter; - private final GridLayoutManager mLayoutManager; private final int mMaxSpanSize; @Nullable private String mAnchorId; - public LimitedBrowseAdapter(BrowseAdapter browseAdapter, GridLayoutManager manager, + public LimitedBrowseAdapter(CarUiRecyclerView recyclerView, BrowseAdapter browseAdapter, BrowseAdapter.Observer browseAdapterObserver) { super(browseAdapter, R.id.browse_list_uxr_config); + mRecyclerView = recyclerView; mBrowseAdapter = browseAdapter; - mLayoutManager = manager; - mMaxSpanSize = manager.getSpanCount(); - mLayoutManager.setSpanSizeLookup(mSpanSizeLookup); + CarUiGridLayoutStyle layoutStyle = (CarUiGridLayoutStyle) mRecyclerView.getLayoutStyle(); + mMaxSpanSize = layoutStyle.getSpanCount(); + layoutStyle.setSpanSizeLookup(mSpanSizeLookup); + mRecyclerView.setLayoutStyle(layoutStyle); mBrowseAdapter.registerObserver(browseAdapterObserver); mBrowseAdapter.setOnListChangedListener(((previousList, currentList) -> { updateUnderlyingDataChanged(currentList.size(), validateAnchor()); @@ -119,17 +123,17 @@ public class LimitedBrowseAdapter extends DelegatingContentLimitingAdapter<Brows } private int getFirstVisibleItemPosition() { - int firstItem = mLayoutManager.findFirstCompletelyVisibleItemPosition(); + int firstItem = mRecyclerView.findFirstCompletelyVisibleItemPosition(); if (firstItem == RecyclerView.NO_POSITION) { - firstItem = mLayoutManager.findFirstVisibleItemPosition(); + firstItem = mRecyclerView.findFirstVisibleItemPosition(); } return firstItem; } private int getLastVisibleItemPosition() { - int lastItem = mLayoutManager.findLastCompletelyVisibleItemPosition(); + int lastItem = mRecyclerView.findLastCompletelyVisibleItemPosition(); if (lastItem == RecyclerView.NO_POSITION) { - lastItem = mLayoutManager.findLastVisibleItemPosition(); + lastItem = mRecyclerView.findLastVisibleItemPosition(); } return lastItem; } diff --git a/src/com/android/car/media/widgets/AppBarController.java b/src/com/android/car/media/widgets/AppBarController.java index 962f2ac..38eccdf 100644 --- a/src/com/android/car/media/widgets/AppBarController.java +++ b/src/com/android/car/media/widgets/AppBarController.java @@ -4,27 +4,32 @@ import android.car.drivingstate.CarUxRestrictions; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; -import android.view.View; +import android.util.Size; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.car.media.MediaAppConfig; import com.android.car.media.R; import com.android.car.media.common.MediaItemMetadata; import com.android.car.media.common.source.MediaSource; import com.android.car.ui.toolbar.MenuItem; -import com.android.car.ui.toolbar.Toolbar; +import com.android.car.ui.toolbar.NavButtonMode; +import com.android.car.ui.toolbar.SearchCapabilities; +import com.android.car.ui.toolbar.SearchConfig; +import com.android.car.ui.toolbar.SearchMode; import com.android.car.ui.toolbar.ToolbarController; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** - * Media template application bar. The callers should set properties via the public methods (e.g., - * {@link #setItems}, {@link #setTitle}, {@link #setHasSettings}), and set the visibility of the - * views via {@link #setState}. A detailed explanation of all possible states of this application - * bar can be seen at {@link Toolbar.State}. + * Media template application bar. This class wraps a {@link ToolbarController} and + * adds media-specific methods to it like {@link #setItems} and {@link #setSearchSupported}. */ public class AppBarController { @@ -32,23 +37,25 @@ public class AppBarController { CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP; private static final int MEDIA_UX_RESTRICTION_NONE = CarUxRestrictions.UX_RESTRICTIONS_BASELINE; - private int mMaxTabs; + private final int mMaxTabs; + private final ArrayList<TabBinder<MediaItemMetadata.ArtworkRef>> mTabs = new ArrayList<>(); private final ToolbarController mToolbarController; + private final Context mApplicationContext; private final boolean mUseSourceLogoForAppSelector; + private final MenuItem mSearch; + private final MenuItem mSettings; + private final MenuItem mEqualizer; + private final MenuItem mAppSelector; + @NonNull private AppBarListener mListener = new AppBarListener(); - private MenuItem mSearch; - private MenuItem mSettings; - private MenuItem mEqualizer; - private MenuItem mAppSelector; - private boolean mSearchSupported; private boolean mShowSearchIfSupported; private String mSearchQuery; - - private Intent mAppSelectorIntent; + private int mSelectedTab = -1; + private Drawable mLogo; /** * Application bar listener @@ -82,20 +89,19 @@ public class AppBarController { public AppBarController(Context context, ToolbarController controller) { mToolbarController = controller; + mApplicationContext = context.getApplicationContext(); mMaxTabs = context.getResources().getInteger(R.integer.max_tabs); mUseSourceLogoForAppSelector = context.getResources().getBoolean(R.bool.use_media_source_logo_for_app_selector); - mAppSelectorIntent = MediaSource.getSourceSelectorIntent(context, false); + Intent appSelectorIntent = MediaSource.getSourceSelectorIntent(context, false); - mToolbarController.registerOnTabSelectedListener(tab -> - mListener.onTabSelected(((MediaItemTab) tab).getItem())); - mToolbarController.registerOnSearchListener(query -> { + mToolbarController.registerSearchListener(query -> { mSearchQuery = query; mListener.onSearch(query); }); - mToolbarController.registerOnSearchCompletedListener( + mToolbarController.registerSearchCompletedListener( () -> mListener.onSearch(mSearchQuery)); mSearch = MenuItem.builder(context) .setToSearch() @@ -116,19 +122,19 @@ public class AppBarController { .setTinted(!mUseSourceLogoForAppSelector) .setIcon(mUseSourceLogoForAppSelector ? null : context.getDrawable(R.drawable.ic_app_switch)) - .setOnClickListener(m -> context.startActivity(mAppSelectorIntent)) + .setOnClickListener(m -> context.startActivity(appSelectorIntent)) .build(); mToolbarController.setMenuItems( Arrays.asList(mSearch, mEqualizer, mSettings, mAppSelector)); - setAppLauncherSupported(mAppSelectorIntent != null); + mAppSelector.setVisible(appSelectorIntent != null); } /** * Sets a listener of this application bar events. In order to avoid memory leaks, consumers * must reset this reference by setting the listener to null. */ - public void setListener(AppBarListener listener) { + public void setListener(@NonNull AppBarListener listener) { mListener = listener; } @@ -138,18 +144,45 @@ public class AppBarController { * @param items list of tabs to show, or null if no tabs should be shown. */ public void setItems(@Nullable List<MediaItemMetadata> items) { - mToolbarController.clearAllTabs(); + if (items == null) { + items = Collections.emptyList(); + } - if (items != null && !items.isEmpty()) { - int count = 0; - for (MediaItemMetadata item : items) { - mToolbarController.addTab(new MediaItemTab(item)); + for (TabBinder<MediaItemMetadata.ArtworkRef> tabBinder : mTabs) { + tabBinder.setUpdateListener(null); + tabBinder.setImage(mApplicationContext, null); + } - count++; - if (count >= mMaxTabs) { - break; - } - } + mTabs.clear(); + + Size maxArtSize = MediaAppConfig.getMediaItemsBitmapMaxSize(mApplicationContext); + for (MediaItemMetadata item : items.subList(0, Math.min(items.size(), mMaxTabs))) { + TabBinder<MediaItemMetadata.ArtworkRef> newTab = new TabBinder<>( + mApplicationContext, + maxArtSize, + item, + item2 -> { + mSelectedTab = mTabs.indexOf(item2); + mListener.onTabSelected(item2.getMediaItemMetadata()); + }); + newTab.setImage(mApplicationContext, item.getArtworkKey()); + mTabs.add(newTab); + } + mSelectedTab = mTabs.isEmpty() ? -1 : 0; + for (TabBinder<MediaItemMetadata.ArtworkRef> tabBinder : mTabs) { + tabBinder.setUpdateListener(x -> updateTabs()); + } + updateTabs(); + } + + private void updateTabs() { + if (mToolbarController.getNavButtonMode() != NavButtonMode.DISABLED) { + mToolbarController.setTabs(Collections.emptyList()); + } else { + mToolbarController.setTabs(mTabs.stream() + .map(TabBinder::getToolbarTab) + .collect(Collectors.toList()), + mSelectedTab); } } @@ -189,21 +222,14 @@ public class AppBarController { } /** - * Sets whether launching app selector is supported - */ - private void setAppLauncherSupported(boolean supported) { - mAppSelector.setVisible(supported); - } - - /** * Updates the currently active item */ public void setActiveItem(MediaItemMetadata item) { - for (int i = 0; i < mToolbarController.getTabCount(); i++) { - MediaItemTab mediaItemTab = (MediaItemTab) mToolbarController.getTab(i); + for (int i = 0; i < mTabs.size(); i++) { + MediaItemMetadata mediaItemMetadata = mTabs.get(i).getMediaItemMetadata(); boolean match = item != null && Objects.equals( item.getId(), - mediaItemTab.getItem().getId()); + mediaItemMetadata.getId()); if (match) { mToolbarController.selectTab(i); return; @@ -216,10 +242,19 @@ public class AppBarController { } public void setLogo(Drawable drawable) { - if (mUseSourceLogoForAppSelector) { - mAppSelector.setIcon(drawable); + mLogo = drawable; + updateLogo(); + } + + private void updateLogo() { + if (mToolbarController.getSearchMode() == SearchMode.DISABLED) { + if (mUseSourceLogoForAppSelector) { + mAppSelector.setIcon(mLogo); + } else { + mToolbarController.setLogo(mLogo); + } } else { - mToolbarController.setLogo(drawable); + mToolbarController.setLogo(null); } } @@ -235,10 +270,6 @@ public class AppBarController { mToolbarController.setTitle(title); } - public void setState(Toolbar.State state) { - mToolbarController.setState(state); - } - public void setMenuItems(List<MenuItem> items) { mToolbarController.setMenuItems(items); } @@ -247,17 +278,30 @@ public class AppBarController { mToolbarController.setBackgroundShown(shown); } - public void setNavButtonMode(Toolbar.NavButtonMode mode) { - mToolbarController.setNavButtonMode(mode); + /** Proxies to {@link ToolbarController#setSearchMode(SearchMode)} */ + public void setSearchMode(SearchMode mode) { + if (mToolbarController.getSearchMode() != mode) { + mToolbarController.setSearchMode(mode); + updateTabs(); + updateLogo(); + } + } + + /** Proxies to {@link ToolbarController#setNavButtonMode(NavButtonMode)} */ + public void setNavButtonMode(NavButtonMode mode) { + if (mode != mToolbarController.getNavButtonMode()) { + mToolbarController.setNavButtonMode(mode); + updateTabs(); + } } - /** See {@link ToolbarController#canShowSearchResultItems}. */ - public boolean canShowSearchResultsView() { - return mToolbarController.canShowSearchResultsView(); + /** Proxies to {@link ToolbarController#getSearchCapabilities()} */ + public SearchCapabilities getSearchCapabilities() { + return mToolbarController.getSearchCapabilities(); } - /** See {@link ToolbarController#setSearchResultsView}. */ - public void setSearchResultsView(View view) { - mToolbarController.setSearchResultsView(view); + /** Proxies to {@link ToolbarController#setSearchConfig(SearchConfig)} */ + public void setSearchConfig(SearchConfig searchConfig) { + mToolbarController.setSearchConfig(searchConfig); } } diff --git a/src/com/android/car/media/widgets/TabBinder.java b/src/com/android/car/media/widgets/TabBinder.java new file mode 100644 index 0000000..ad728f6 --- /dev/null +++ b/src/com/android/car/media/widgets/TabBinder.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.car.media.widgets; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.Size; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.car.apps.common.CommonFlags; +import com.android.car.apps.common.imaging.ImageBinder; +import com.android.car.media.R; +import com.android.car.media.common.MediaItemMetadata; +import com.android.car.ui.toolbar.Tab; + +import java.util.function.Consumer; + +/** + * A {@link ImageBinder} that exposes a downloads images from a {@link MediaItemMetadata} + * into a toolbar tab. + * + * The resulting tab can be retrieved from the {@link #getToolbarTab} method. + * There are two listeners that must be supplied in the constructor: a selected + * listener and an update listener. The selected listener is called when the + * tab is selected. The update listener is called when the tab has changed in + * some way, and the updated version needs to be retrieved from {@link #getToolbarTab} + * again. + * + * @param <T> The type of {@link ImageBinder.ImageRef} to use. + */ +public class TabBinder<T extends ImageBinder.ImageRef> extends ImageBinder<T> { + private final MediaItemMetadata mMediaItemMetadata; + private final Context mContext; + + private Consumer<TabBinder<T>> mUpdateListener; + private Tab mTab; + + protected TabBinder( + @NonNull Context context, + @NonNull Size maxImageSize, + @NonNull MediaItemMetadata item, + @NonNull Consumer<TabBinder<T>> selectedListener) { + super(PlaceholderType.NONE, maxImageSize); + mContext = context; + mMediaItemMetadata = item; + CharSequence title = mMediaItemMetadata.getTitle(); + mTab = Tab.builder() + .setText(title == null ? null : title.toString()) + .setSelectedListener(tab -> selectedListener.accept(this)) + .build(); + } + + @Override + protected void setDrawable(@Nullable Drawable drawable) { + boolean shouldUseToolbarTint = true; + CommonFlags flags = CommonFlags.getInstance(mContext); + if (flags.shouldFlagImproperImageRefs()) { + if (drawable instanceof BitmapDrawable) { + int tint = mContext.getColor( + R.color.improper_image_refs_tint_color); + drawable.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP)); + shouldUseToolbarTint = false; + } + } + + mTab = mTab.copy() + .setIcon(drawable) + .setTinted(shouldUseToolbarTint) + .build(); + if (mUpdateListener != null) { + mUpdateListener.accept(this); + } + } + + public void setUpdateListener(Consumer<TabBinder<T>> updateListener) { + mUpdateListener = updateListener; + } + + public MediaItemMetadata getMediaItemMetadata() { + return mMediaItemMetadata; + } + + public Tab getToolbarTab() { + return mTab; + } +} diff --git a/tools/generate-overlayable.sh b/tools/generate-overlayable.sh index 833d9f5..b6fe78a 100755 --- a/tools/generate-overlayable.sh +++ b/tools/generate-overlayable.sh @@ -22,8 +22,8 @@ fi PROJECT_TOP=$ANDROID_BUILD_TOP/packages/apps/Car/Media -python $ANDROID_BUILD_TOP/packages/apps/Car/tests/tools/rro/generate-overlayable.py \ +python3 $ANDROID_BUILD_TOP/packages/apps/Car/tests/tools/rro/generate-overlayable.py \ -n CarMediaApp \ -r $PROJECT_TOP/res \ - -e $PROJECT_TOP/res/values/overlayable.xml $PROJECT_TOP/res/xml/automotive_app_desc.xml \ + -e $PROJECT_TOP/res/values/overlayable.xml $PROJECT_TOP/res/xml/automotive_app_desc.xml $PROJECT_TOP/res/values/colors.xml $PROJECT_TOP/res/values/dimens.xml $PROJECT_TOP/res/color/progress_bar_thumb_inner_ring_color.xml $PROJECT_TOP/res/color/progress_bar_thumb_outer_ring_color.xml \ -o $PROJECT_TOP/res/values/overlayable.xml
\ No newline at end of file |