1 package com.android.car.media.widgets;
2 
3 import android.car.drivingstate.CarUxRestrictions;
4 import android.content.Context;
5 import android.content.Intent;
6 import android.graphics.drawable.Drawable;
7 import android.util.Size;
8 
9 import androidx.annotation.NonNull;
10 import androidx.annotation.Nullable;
11 
12 import com.android.car.media.MediaAppConfig;
13 import com.android.car.media.R;
14 import com.android.car.media.common.MediaItemMetadata;
15 import com.android.car.media.common.source.MediaSource;
16 import com.android.car.ui.toolbar.MenuItem;
17 import com.android.car.ui.toolbar.NavButtonMode;
18 import com.android.car.ui.toolbar.SearchCapabilities;
19 import com.android.car.ui.toolbar.SearchConfig;
20 import com.android.car.ui.toolbar.SearchMode;
21 import com.android.car.ui.toolbar.ToolbarController;
22 
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.stream.Collectors;
29 
30 /**
31  * Media template application bar. This class wraps a {@link ToolbarController} and
32  * adds media-specific methods to it like {@link #setItems} and {@link #setSearchSupported}.
33  */
34 public class AppBarController {
35 
36     private static final int MEDIA_UX_RESTRICTION_DEFAULT =
37             CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP;
38     private static final int MEDIA_UX_RESTRICTION_NONE = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
39 
40     private final int mMaxTabs;
41     private final ArrayList<TabBinder<MediaItemMetadata.ArtworkRef>> mTabs = new ArrayList<>();
42     private final ToolbarController mToolbarController;
43     private final Context mApplicationContext;
44 
45     private final boolean mUseSourceLogoForAppSelector;
46 
47     private final MenuItem mSearch;
48     private final MenuItem mSettings;
49     private final MenuItem mEqualizer;
50     private final MenuItem mAppSelector;
51 
52     @NonNull
53     private AppBarListener mListener = new AppBarListener();
54     private boolean mSearchSupported;
55     private boolean mShowSearchIfSupported;
56     private String mSearchQuery;
57     private int mSelectedTab = -1;
58     private Drawable mLogo;
59 
60     /**
61      * Application bar listener
62      */
63     public static class AppBarListener {
64         /**
65          * Invoked when the user selects an item from the tabs
66          */
onTabSelected(MediaItemMetadata item)67         protected void onTabSelected(MediaItemMetadata item) {}
68 
69         /**
70          * Invoked when the user clicks on the settings button.
71          */
onSettingsSelection()72         protected void onSettingsSelection() {}
73 
74         /**
75          * Invoked when the user clicks on the equalizer button.
76          */
onEqualizerSelection()77         protected void onEqualizerSelection() {}
78 
79         /**
80          * Invoked when the user submits a search query.
81          */
onSearch(String query)82         protected void onSearch(String query) {}
83 
84         /**
85          * Invoked when the user clicks on the search button
86          */
onSearchSelection()87         protected void onSearchSelection() {}
88     }
89 
AppBarController(Context context, ToolbarController controller)90     public AppBarController(Context context, ToolbarController controller) {
91         mToolbarController = controller;
92         mApplicationContext = context.getApplicationContext();
93         mMaxTabs = context.getResources().getInteger(R.integer.max_tabs);
94 
95         mUseSourceLogoForAppSelector =
96                 context.getResources().getBoolean(R.bool.use_media_source_logo_for_app_selector);
97 
98         Intent appSelectorIntent = MediaSource.getSourceSelectorIntent(context, false);
99 
100         mToolbarController.registerSearchListener(query -> {
101             mSearchQuery = query;
102             mListener.onSearch(query);
103         });
104         mToolbarController.registerSearchCompletedListener(
105                 () -> mListener.onSearch(mSearchQuery));
106         mSearch = MenuItem.builder(context)
107                 .setToSearch()
108                 .setOnClickListener(v -> mListener.onSearchSelection())
109                 .build();
110         mSettings = MenuItem.builder(context)
111                 .setToSettings()
112                 .setUxRestrictions(MEDIA_UX_RESTRICTION_DEFAULT)
113                 .setOnClickListener(v -> mListener.onSettingsSelection())
114                 .build();
115         mEqualizer = MenuItem.builder(context)
116                 .setTitle(R.string.menu_item_sound_settings_title)
117                 .setIcon(R.drawable.ic_equalizer)
118                 .setOnClickListener(v -> mListener.onEqualizerSelection())
119                 .build();
120         mAppSelector = MenuItem.builder(context)
121                 .setTitle(R.string.menu_item_app_selector_title)
122                 .setTinted(!mUseSourceLogoForAppSelector)
123                 .setIcon(mUseSourceLogoForAppSelector
124                         ? null : context.getDrawable(R.drawable.ic_app_switch))
125                 .setOnClickListener(m -> context.startActivity(appSelectorIntent))
126                 .build();
127         mToolbarController.setMenuItems(
128                 Arrays.asList(mSearch, mEqualizer, mSettings, mAppSelector));
129 
130         mAppSelector.setVisible(appSelectorIntent != null);
131     }
132 
133     /**
134      * Sets a listener of this application bar events. In order to avoid memory leaks, consumers
135      * must reset this reference by setting the listener to null.
136      */
setListener(@onNull AppBarListener listener)137     public void setListener(@NonNull AppBarListener listener) {
138         mListener = listener;
139     }
140 
141     /**
142      * Updates the list of items to show in the application bar tabs.
143      *
144      * @param items list of tabs to show, or null if no tabs should be shown.
145      */
setItems(@ullable List<MediaItemMetadata> items)146     public void setItems(@Nullable List<MediaItemMetadata> items) {
147         if (items == null) {
148             items = Collections.emptyList();
149         }
150 
151         for (TabBinder<MediaItemMetadata.ArtworkRef> tabBinder : mTabs) {
152             tabBinder.setUpdateListener(null);
153             tabBinder.setImage(mApplicationContext, null);
154         }
155 
156         mTabs.clear();
157 
158         Size maxArtSize = MediaAppConfig.getMediaItemsBitmapMaxSize(mApplicationContext);
159         for (MediaItemMetadata item : items.subList(0, Math.min(items.size(), mMaxTabs))) {
160             TabBinder<MediaItemMetadata.ArtworkRef> newTab = new TabBinder<>(
161                     mApplicationContext,
162                     maxArtSize,
163                     item,
164                     item2 -> {
165                         mSelectedTab = mTabs.indexOf(item2);
166                         mListener.onTabSelected(item2.getMediaItemMetadata());
167                     });
168             newTab.setImage(mApplicationContext, item.getArtworkKey());
169             mTabs.add(newTab);
170         }
171         mSelectedTab = mTabs.isEmpty() ? -1 : 0;
172         for (TabBinder<MediaItemMetadata.ArtworkRef> tabBinder : mTabs) {
173             tabBinder.setUpdateListener(x -> updateTabs());
174         }
175         updateTabs();
176     }
177 
updateTabs()178     private void updateTabs() {
179         if (mToolbarController.getNavButtonMode() != NavButtonMode.DISABLED) {
180             mToolbarController.setTabs(Collections.emptyList());
181         } else {
182             mToolbarController.setTabs(mTabs.stream()
183                             .map(TabBinder::getToolbarTab)
184                             .collect(Collectors.toList()),
185                     mSelectedTab);
186         }
187     }
188 
189     /** Sets whether the source has settings (not all screens show it). */
setHasSettings(boolean hasSettings)190     public void setHasSettings(boolean hasSettings) {
191         mSettings.setVisible(hasSettings);
192     }
193 
194     /** Sets whether the source's settings is distraction optimized. */
setSettingsDistractionOptimized(boolean isDistractionOptimized)195     public void setSettingsDistractionOptimized(boolean isDistractionOptimized) {
196         mSettings.setUxRestrictions(isDistractionOptimized
197                 ? MEDIA_UX_RESTRICTION_NONE
198                 : MEDIA_UX_RESTRICTION_DEFAULT);
199     }
200 
201     /** Sets whether the source has equalizer support. */
setHasEqualizer(boolean hasEqualizer)202     public void setHasEqualizer(boolean hasEqualizer) {
203         mEqualizer.setVisible(hasEqualizer);
204     }
205 
206     /**
207      * Sets whether search is supported
208      */
setSearchSupported(boolean supported)209     public void setSearchSupported(boolean supported) {
210         mSearchSupported = supported;
211         updateSearchVisibility();
212     }
213 
214     /** Sets whether to show the search MenuItem if supported */
showSearchIfSupported(boolean show)215     public void showSearchIfSupported(boolean show) {
216         mShowSearchIfSupported = show;
217         updateSearchVisibility();
218     }
219 
updateSearchVisibility()220     private void updateSearchVisibility() {
221         mSearch.setVisible(mShowSearchIfSupported && mSearchSupported);
222     }
223 
224     /**
225      * Updates the currently active item
226      */
setActiveItem(MediaItemMetadata item)227     public void setActiveItem(MediaItemMetadata item) {
228         for (int i = 0; i < mTabs.size(); i++) {
229             MediaItemMetadata mediaItemMetadata = mTabs.get(i).getMediaItemMetadata();
230             boolean match = item != null && Objects.equals(
231                     item.getId(),
232                     mediaItemMetadata.getId());
233             if (match) {
234                 mToolbarController.selectTab(i);
235                 return;
236             }
237         }
238     }
239 
setSearchQuery(String query)240     public void setSearchQuery(String query) {
241         mToolbarController.setSearchQuery(query);
242     }
243 
setLogo(Drawable drawable)244     public void setLogo(Drawable drawable) {
245         mLogo = drawable;
246         updateLogo();
247     }
248 
updateLogo()249     private void updateLogo() {
250         if (mToolbarController.getSearchMode() == SearchMode.DISABLED) {
251             if (mUseSourceLogoForAppSelector) {
252                 mAppSelector.setIcon(mLogo);
253             } else {
254                 mToolbarController.setLogo(mLogo);
255             }
256         } else {
257             mToolbarController.setLogo(null);
258         }
259     }
260 
setSearchIcon(Drawable drawable)261     public void setSearchIcon(Drawable drawable) {
262         mToolbarController.setSearchIcon(drawable);
263     }
264 
setTitle(CharSequence title)265     public void setTitle(CharSequence title) {
266         mToolbarController.setTitle(title);
267     }
268 
setTitle(int title)269     public void setTitle(int title) {
270         mToolbarController.setTitle(title);
271     }
272 
setMenuItems(List<MenuItem> items)273     public void setMenuItems(List<MenuItem> items) {
274         mToolbarController.setMenuItems(items);
275     }
276 
setBackgroundShown(boolean shown)277     public void setBackgroundShown(boolean shown) {
278         mToolbarController.setBackgroundShown(shown);
279     }
280 
281     /** Proxies to {@link ToolbarController#setSearchMode(SearchMode)} */
setSearchMode(SearchMode mode)282     public void setSearchMode(SearchMode mode) {
283         if (mToolbarController.getSearchMode() != mode) {
284             mToolbarController.setSearchMode(mode);
285             updateTabs();
286             updateLogo();
287         }
288     }
289 
290     /** Proxies to {@link ToolbarController#setNavButtonMode(NavButtonMode)} */
setNavButtonMode(NavButtonMode mode)291     public void setNavButtonMode(NavButtonMode mode) {
292         if (mode != mToolbarController.getNavButtonMode()) {
293             mToolbarController.setNavButtonMode(mode);
294             updateTabs();
295         }
296     }
297 
298     /** Proxies to {@link ToolbarController#getSearchCapabilities()} */
getSearchCapabilities()299     public SearchCapabilities getSearchCapabilities() {
300         return mToolbarController.getSearchCapabilities();
301     }
302 
303     /** Proxies to {@link ToolbarController#setSearchConfig(SearchConfig)} */
setSearchConfig(SearchConfig searchConfig)304     public void setSearchConfig(SearchConfig searchConfig) {
305         mToolbarController.setSearchConfig(searchConfig);
306     }
307 }
308