• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.car.media.widgets;
2 
3 import android.annotation.Nullable;
4 import android.content.Context;
5 import android.graphics.drawable.Drawable;
6 import android.util.AttributeSet;
7 import android.util.Log;
8 import android.view.LayoutInflater;
9 import android.view.View;
10 import android.view.ViewGroup;
11 import android.widget.ImageView;
12 import android.widget.TextView;
13 
14 import androidx.constraintlayout.widget.ConstraintLayout;
15 
16 import com.android.car.apps.common.UxrButton;
17 import com.android.car.apps.common.widget.CarTabLayout;
18 import com.android.car.media.R;
19 import com.android.car.media.common.MediaAppSelectorWidget;
20 import com.android.car.media.common.MediaItemMetadata;
21 
22 import java.util.List;
23 import java.util.Objects;
24 
25 /**
26  * Media template application bar. The callers should set properties via the public methods (e.g.,
27  * {@link setItems()}, {@link setTitle()}, {@link setHasSettings()}), and set the visibility of the
28  * views via {@link setState()}. A detailed explanation of all possible states of this application
29  * bar can be seen at {@link AppBarView.State}.
30  */
31 public class AppBarView extends ConstraintLayout {
32     private static final String TAG = "AppBarView";
33 
34     private CarTabLayout<MediaItemTab> mTabsContainer;
35     private ImageView mNavIcon;
36     private ViewGroup mNavIconContainer;
37     private TextView mTitle;
38     /** Visible if mHasSettings && mShowSettings. */
39     private UxrButton mSettingsButton;
40     private boolean mHasSettings;
41     private boolean mShowSettings;
42     private View mSearchButton;
43     private SearchBar mSearchBar;
44     private MediaAppSelectorWidget mAppSelector;
45     private Context mContext;
46     private int mMaxTabs;
47     private Drawable mArrowBack;
48     private Drawable mCollapse;
49     private State mState = State.BROWSING;
50     private AppBarListener mListener;
51     private int mFadeDuration;
52     private String mMediaAppTitle;
53     private boolean mSearchSupported;
54     private int mMaxRows;
55 
56     public interface AppBarProvider {
getAppBar()57         AppBarView getAppBar();
58     }
59 
60     /**
61      * Application bar listener
62      */
63     public interface AppBarListener {
64         /**
65          * Invoked when the user selects an item from the tabs
66          */
onTabSelected(MediaItemMetadata item)67         void onTabSelected(MediaItemMetadata item);
68 
69         /**
70          * Invoked when the user clicks on the back button
71          */
onBack()72         void onBack();
73 
74         /**
75          * Invoked when the user clicks on the settings button.
76          */
onSettingsSelection()77         void onSettingsSelection();
78 
79         /**
80          * Invoked when the user submits a search query.
81          */
onSearch(String query)82         void onSearch(String query);
83 
84         /**
85          * Invoked when the user clicks on the search button
86          */
onSearchSelection()87         void onSearchSelection();
88     }
89 
90     /**
91      * Possible states of this application bar
92      */
93     public enum State {
94         /**
95          * Normal application state. If we are able to obtain media items from the media
96          * source application, we display them as tabs. Otherwise we show the application name.
97          */
98         BROWSING,
99         /**
100          * Indicates that the user has navigated into an element. In this case we show
101          * the name of the element and we disable the back button.
102          */
103         STACKED,
104         /**
105          * Indicates that the user is currently entering a search query. We show the search bar and
106          * a collapse icon
107          */
108         SEARCHING,
109         /**
110          * Used whenever the app bar should not display any information such as when MediaCenter
111          * is in an error state
112          */
113         EMPTY
114     }
115 
AppBarView(Context context)116     public AppBarView(Context context) {
117         this(context, null);
118     }
119 
AppBarView(Context context, AttributeSet attrs)120     public AppBarView(Context context, AttributeSet attrs) {
121         this(context, attrs, 0);
122     }
123 
AppBarView(Context context, AttributeSet attrs, int defStyleAttr)124     public AppBarView(Context context, AttributeSet attrs, int defStyleAttr) {
125         super(context, attrs, defStyleAttr);
126         init(context, attrs, defStyleAttr);
127     }
128 
init(Context context, AttributeSet attrs, int defStyleAttr)129     private void init(Context context, AttributeSet attrs, int defStyleAttr) {
130         mMaxTabs = context.getResources().getInteger(R.integer.max_tabs);
131 
132         LayoutInflater inflater = (LayoutInflater) context
133                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
134         inflater.inflate(R.layout.appbar_view, this, true);
135 
136         mContext = context;
137         mMaxRows = mContext.getResources().getInteger(R.integer.num_app_bar_view_rows);
138 
139         mTabsContainer = findViewById(R.id.tabs);
140         mTabsContainer.addOnCarTabSelectedListener(
141                 new CarTabLayout.SimpleOnCarTabSelectedListener<MediaItemTab>() {
142                     @Override
143                     public void onCarTabSelected(MediaItemTab mediaItemTab) {
144                         if (mListener != null) {
145                             mListener.onTabSelected(mediaItemTab.getItem());
146                         }
147                     }
148                 });
149         mNavIcon = findViewById(R.id.nav_icon);
150         mNavIconContainer = findViewById(R.id.nav_icon_container);
151         mNavIconContainer.setOnClickListener(view -> onNavIconClicked());
152         mAppSelector = findViewById(R.id.app_switch_container);
153         mSettingsButton = findViewById(R.id.settings);
154         mSettingsButton.setOnClickListener(view -> onSettingsClicked());
155         mSearchButton = findViewById(R.id.search);
156         mSearchButton.setOnClickListener(view -> onSearchClicked());
157         mSearchBar = findViewById(R.id.search_bar_container);
158 
159         mTitle = findViewById(R.id.title);
160         mArrowBack = getResources().getDrawable(R.drawable.ic_arrow_back, null);
161         mCollapse = getResources().getDrawable(R.drawable.ic_expand_more, null);
162         mFadeDuration = getResources().getInteger(R.integer.app_selector_fade_duration);
163         mMediaAppTitle = getResources().getString(R.string.media_app_title);
164 
165         setState(State.BROWSING);
166     }
167 
openAppSelector()168     public void openAppSelector() {
169         mAppSelector.open();
170     }
171 
closeAppSelector()172     public void closeAppSelector() {
173         mAppSelector.close();
174     }
175 
onNavIconClicked()176     private void onNavIconClicked() {
177         if (mListener == null) {
178             return;
179         }
180         switch (mState) {
181             case BROWSING:
182             case STACKED:
183                 mListener.onBack();
184                 break;
185             case SEARCHING:
186                 mSearchBar.showSearchBar(false);
187                 mListener.onBack();
188                 break;
189         }
190     }
191 
onSettingsClicked()192     private void onSettingsClicked() {
193         if (mListener == null) {
194             return;
195         }
196         mListener.onSettingsSelection();
197     }
198 
onSearchClicked()199     private void onSearchClicked() {
200         if (mListener == null) {
201             return;
202         }
203         mListener.onSearchSelection();
204     }
205 
206     /**
207      * Sets a listener of this application bar events. In order to avoid memory leaks, consumers
208      * must reset this reference by setting the listener to null.
209      */
setListener(AppBarListener listener)210     public void setListener(AppBarListener listener) {
211         mListener = listener;
212     }
213 
214     /**
215      * Updates the list of items to show in the application bar tabs.
216      *
217      * @param items list of tabs to show, or null if no tabs should be shown.
218      */
setItems(@ullable List<MediaItemMetadata> items)219     public void setItems(@Nullable List<MediaItemMetadata> items) {
220         mTabsContainer.clearAllCarTabs();
221 
222         if (items != null && !items.isEmpty()) {
223             int count = 0;
224             for (MediaItemMetadata item : items) {
225                 MediaItemTab tab = new MediaItemTab(mContext, item);
226                 mTabsContainer.addCarTab(tab);
227 
228                 count++;
229                 if (count >= mMaxTabs) {
230                     break;
231                 }
232             }
233         }
234 
235         // Refresh the views visibility
236         setState(mState);
237     }
238 
239     /**
240      * Updates the title to display when the bar is not showing tabs. If the provided title is null,
241      * will default to displaying the app name.
242      */
setTitle(CharSequence title)243     public void setTitle(CharSequence title) {
244         mTitle.setText(title != null ? title : mMediaAppTitle);
245     }
246 
247     /**
248      * Sets the name of the currently displayed media app. This is used as the default title for
249      * playback and the root browse menu. If provided title is null, will use default media center
250      * title.
251      */
setMediaAppTitle(CharSequence appTitle)252     public void setMediaAppTitle(CharSequence appTitle) {
253         mMediaAppTitle = appTitle == null ? getResources().getString(R.string.media_app_title)
254                 : appTitle.toString();
255     }
256 
257     /** Sets whether the source has settings (not all screens show it). */
setHasSettings(boolean hasSettings)258     public void setHasSettings(boolean hasSettings) {
259         mHasSettings = hasSettings;
260         updateSettingsVisibility();
261     }
262 
showSettings(boolean showSettings)263     private void showSettings(boolean showSettings) {
264         mShowSettings = showSettings;
265         updateSettingsVisibility();
266     }
267 
updateSettingsVisibility()268     private void updateSettingsVisibility() {
269         mSettingsButton.setVisibility(mHasSettings && mShowSettings ? VISIBLE : GONE);
270     }
271 
272     /**
273      * Updates the currently active item
274      */
setActiveItem(MediaItemMetadata item)275     public void setActiveItem(MediaItemMetadata item) {
276         for (int i = 0; i < mTabsContainer.getCarTabCount(); i++) {
277             MediaItemTab mediaItemTab = mTabsContainer.get(i);
278             boolean match = item != null && Objects.equals(
279                     item.getId(),
280                     mediaItemTab.getItem().getId());
281             if (match) {
282                 mTabsContainer.selectCarTab(mediaItemTab);
283                 return;
284             }
285         }
286     }
287 
288     /**
289      * Sets whether the search box should be shown
290      */
setSearchSupported(boolean supported)291     public void setSearchSupported(boolean supported) {
292         mSearchSupported = supported;
293         mSearchButton.setVisibility(mSearchSupported ? View.VISIBLE : View.GONE);
294     }
295 
296     /**
297      * Sets whether to show tabs or not. The caller should make sure tabs has at least 1 item before
298      * showing tabs.
299      */
setShowTabs(boolean visible)300     private void setShowTabs(boolean visible) {
301         // Refresh state to adjust for new tab visibility
302         mTabsContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
303     }
304 
305     /**
306      * Updates the state of the bar.
307      */
setState(State state)308     public void setState(State state) {
309         mState = state;
310         final boolean hasTabs = mTabsContainer.getCarTabCount() > 0;
311         final boolean showTitle = !hasTabs || mMaxRows == 2;
312         Log.d(TAG, "Updating state: " + state + " (has tabs: " + hasTabs + ")");
313         switch (state) {
314             case EMPTY:
315                 mNavIconContainer.setVisibility(View.GONE);
316                 setShowTabs(false);
317                 mTitle.setVisibility(View.GONE);
318                 mSearchBar.showSearchBar(false);
319                 showSettings(true);
320                 mAppSelector.setVisibility(View.VISIBLE);
321                 break;
322             case BROWSING:
323                 mNavIcon.setImageDrawable(mArrowBack);
324                 mNavIconContainer.setVisibility(View.GONE);
325                 setShowTabs(hasTabs);
326                 mTitle.setVisibility(showTitle ? View.VISIBLE : View.GONE);
327                 mSearchBar.showSearchBar(false);
328                 mSearchButton.setVisibility(mSearchSupported ? View.VISIBLE : View.GONE);
329                 showSettings(true);
330                 mAppSelector.setVisibility(View.VISIBLE);
331                 break;
332             case STACKED:
333                 mNavIcon.setImageDrawable(mArrowBack);
334                 mNavIconContainer.setVisibility(View.VISIBLE);
335                 setShowTabs(false);
336                 mTitle.setVisibility(View.VISIBLE);
337                 mSearchBar.showSearchBar(false);
338                 mSearchButton.setVisibility(mSearchSupported ? View.VISIBLE : View.GONE);
339                 showSettings(true);
340                 mAppSelector.setVisibility(View.VISIBLE);
341                 break;
342             case SEARCHING:
343                 mNavIcon.setImageDrawable(mArrowBack);
344                 mNavIconContainer.setVisibility(View.VISIBLE);
345                 setShowTabs(false);
346                 mTitle.setVisibility(View.GONE);
347                 mSearchBar.showSearchBar(true);
348                 mSearchButton.setVisibility(View.GONE);
349                 showSettings(false);
350                 mAppSelector.setVisibility(View.GONE);
351                 break;
352         }
353     }
354 }
355