• 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.content.res.TypedArray;
6 import android.graphics.Bitmap;
7 import android.graphics.drawable.Drawable;
8 import android.support.design.widget.TabLayout;
9 import android.transition.Fade;
10 import android.transition.Transition;
11 import android.transition.TransitionManager;
12 import android.util.AttributeSet;
13 import android.util.Log;
14 import android.util.TypedValue;
15 import android.view.LayoutInflater;
16 import android.view.View;
17 import android.view.ViewGroup;
18 import android.widget.ImageView;
19 import android.widget.LinearLayout;
20 import android.widget.RelativeLayout;
21 import android.widget.TextView;
22 
23 import com.android.car.media.R;
24 import com.android.car.media.common.MediaItemMetadata;
25 
26 import java.util.List;
27 import java.util.Objects;
28 
29 /**
30  * Media template application bar. A detailed explanation of all possible states of this
31  * application bar can be seen at {@link AppBarView.State}.
32  */
33 public class AppBarView extends RelativeLayout {
34     private static final String TAG = "AppBarView";
35     /** Default number of tabs to show on this app bar */
36     private static int DEFAULT_MAX_TABS = 4;
37 
38     private LinearLayout mTabsContainer;
39     private ImageView mAppIcon;
40     private ImageView mAppSwitchIcon;
41     private ImageView mNavIcon;
42     private ViewGroup mNavIconContainer;
43     private TextView mTitle;
44     private ViewGroup mAppSwitchContainer;
45     private Context mContext;
46     private int mMaxTabs;
47     private Drawable mArrowDropDown;
48     private Drawable mArrowDropUp;
49     private Drawable mArrowBack;
50     private Drawable mCollapse;
51     private State mState = State.BROWSING;
52     private AppBarListener mListener;
53     private int mFadeDuration;
54     private float mSelectedTabAlpha;
55     private float mUnselectedTabAlpha;
56     private MediaItemMetadata mSelectedItem;
57     private String mMediaAppTitle;
58     private Drawable mDefaultIcon;
59     private boolean mContentForwardEnabled;
60 
61     /**
62      * Application bar listener
63      */
64     public interface AppBarListener {
65         /**
66          * Invoked when the user selects an item from the tabs
67          */
onTabSelected(MediaItemMetadata item)68         void onTabSelected(MediaItemMetadata item);
69 
70         /**
71          * Invoked when the user clicks on the back button
72          */
onBack()73         void onBack();
74 
75         /**
76          * Invoked when the user clicks on the collapse button
77          */
onCollapse()78         void onCollapse();
79 
80         /**
81          * Invoked when the user clicks on the app selection switch
82          */
onAppSelection()83         void onAppSelection();
84     }
85 
86     /**
87      * Possible states of this application bar
88      */
89     public enum State {
90         /**
91          * Normal application state. If we are able to obtain media items from the media
92          * source application, we display them as tabs. Otherwise we show the application name.
93          */
94         BROWSING,
95         /**
96          * Indicates that the user has navigated into an element. In this case we show
97          * the name of the element and we disable the back button.
98          */
99         STACKED,
100         /**
101          * Indicates that we have expanded a view that can be collapsed. We show the
102          * title of the application and a collapse icon
103          */
104         PLAYING,
105         /**
106          * Used to indicate that the user is inside the app selector. In this case we disable
107          * navigation, we show the title of the application and we show the app switch icon
108          * point up
109          */
110         APP_SELECTION
111     }
112 
AppBarView(Context context)113     public AppBarView(Context context) {
114         this(context, null);
115     }
116 
AppBarView(Context context, AttributeSet attrs)117     public AppBarView(Context context, AttributeSet attrs) {
118         this(context, attrs, 0);
119     }
120 
AppBarView(Context context, AttributeSet attrs, int defStyleAttr)121     public AppBarView(Context context, AttributeSet attrs, int defStyleAttr) {
122         this(context, attrs, defStyleAttr, 0);
123     }
124 
AppBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)125     public AppBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
126         super(context, attrs, defStyleAttr, defStyleRes);
127         init(context, attrs, defStyleAttr, defStyleRes);
128     }
129 
init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)130     private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
131         TypedArray ta = context.obtainStyledAttributes(
132                 attrs, R.styleable.AppBarView, defStyleAttr, defStyleRes);
133         mMaxTabs = ta.getInteger(R.styleable.AppBarView_max_tabs, DEFAULT_MAX_TABS);
134         ta.recycle();
135 
136         LayoutInflater inflater = (LayoutInflater) context
137                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
138         inflater.inflate(R.layout.appbar_view, this, true);
139 
140         mContext = context;
141         mTabsContainer = findViewById(R.id.tabs);
142         mNavIcon = findViewById(R.id.nav_icon);
143         mNavIconContainer = findViewById(R.id.nav_icon_container);
144         mNavIconContainer.setOnClickListener(view -> onNavIconClicked());
145         mAppIcon = findViewById(R.id.app_icon);
146         mAppSwitchIcon = findViewById(R.id.app_switch_icon);
147         mAppSwitchContainer = findViewById(R.id.app_switch_container);
148         mAppSwitchContainer.setOnClickListener(view -> onAppSwitchClicked());
149         mTitle = findViewById(R.id.title);
150         mArrowDropDown = getResources().getDrawable(R.drawable.ic_arrow_drop_down, null);
151         mArrowDropUp = getResources().getDrawable(R.drawable.ic_arrow_drop_up, null);
152         mArrowBack = getResources().getDrawable(R.drawable.ic_arrow_back, null);
153         mCollapse = getResources().getDrawable(R.drawable.ic_expand_more, null);
154         mFadeDuration = getResources().getInteger(R.integer.app_selector_fade_duration);
155         TypedValue outValue = new TypedValue();
156         getResources().getValue(R.dimen.browse_tab_alpha_selected, outValue, true);
157         mSelectedTabAlpha = outValue.getFloat();
158         getResources().getValue(R.dimen.browse_tab_alpha_unselected, outValue, true);
159         mUnselectedTabAlpha = outValue.getFloat();
160         mMediaAppTitle = getResources().getString(R.string.media_app_title);
161         mDefaultIcon = getResources().getDrawable(R.drawable.ic_music);
162 
163         setState(State.BROWSING);
164     }
165 
onNavIconClicked()166     private void onNavIconClicked() {
167         if (mListener == null) {
168             return;
169         }
170         switch (mState) {
171             case STACKED:
172                 mListener.onBack();
173                 break;
174             case PLAYING:
175                 mListener.onCollapse();
176                 break;
177         }
178     }
179 
onAppSwitchClicked()180     private void onAppSwitchClicked() {
181         if (mListener == null) {
182             return;
183         }
184         mListener.onAppSelection();
185     }
186 
187     /**
188      * Sets a listener of this application bar events. In order to avoid memory leaks, consumers
189      * must reset this reference by setting the listener to null.
190      */
setListener(AppBarListener listener)191     public void setListener(AppBarListener listener) {
192         mListener = listener;
193     }
194 
195     /**
196      * Updates the list of items to show in the application bar tabs.
197      *
198      * @param items list of tabs to show, or null if no tabs should be shown.
199      */
setItems(@ullable List<MediaItemMetadata> items)200     public void setItems(@Nullable List<MediaItemMetadata> items) {
201         mTabsContainer.removeAllViews();
202 
203         if (items != null) {
204             int count = 0;
205             int padding = mContext.getResources().getDimensionPixelSize(R.dimen.car_padding_4);
206             int tabWidth = mContext.getResources().getDimensionPixelSize(R.dimen.browse_tab_width) +
207                     2 * padding;
208             LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
209                     tabWidth, ViewGroup.LayoutParams.MATCH_PARENT);
210             for (MediaItemMetadata item : items) {
211                 MediaItemTabView tab = new MediaItemTabView(mContext, item);
212                 mTabsContainer.addView(tab);
213                 tab.setLayoutParams(layoutParams);
214                 tab.setOnClickListener(view -> {
215                     if (mListener != null) {
216                         mListener.onTabSelected(item);
217                     }
218                 });
219                 tab.setPadding(padding, 0, padding, 0);
220                 tab.requestLayout();
221                 tab.setTag(item);
222 
223                 count++;
224                 if (count >= mMaxTabs) {
225                     break;
226                 }
227             }
228         }
229 
230         // Refresh the views visibility
231         setState(mState);
232     }
233 
234     /**
235      * Updates the title to display when the bar is not showing tabs.
236      */
setTitle(CharSequence title)237     public void setTitle(CharSequence title) {
238         mTitle.setText(title != null ? title : mMediaAppTitle);
239     }
240 
241     /**
242      * Whether content forward browsing is enabled or not
243      */
setContentForwardEnabled(boolean enabled)244     public void setContentForwardEnabled(boolean enabled) {
245         mContentForwardEnabled = enabled;
246     }
247 
248     /**
249      * Updates the application icon to show next to the application switcher.
250      */
setAppIcon(Bitmap icon)251     public void setAppIcon(Bitmap icon) {
252         if (icon != null) {
253             mAppIcon.setImageBitmap(icon);
254         } else {
255             mAppIcon.setImageDrawable(mDefaultIcon);
256         }
257     }
258 
259     /**
260      * Indicates whether or not the application switcher should be enabled.
261      */
setAppSelection(boolean enabled)262     public void setAppSelection(boolean enabled) {
263         mAppSwitchIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
264     }
265 
266     /**
267      * Updates the currently active item
268      */
setActiveItem(MediaItemMetadata item)269     public void setActiveItem(MediaItemMetadata item) {
270         mSelectedItem = item;
271         // TODO(b/79264184): Updating tabs alpha is causing them to disappear randomly. We are
272         // de-activating this feature for not.
273         // updateTabs();
274     }
275 
updateTabs()276     private void updateTabs() {
277         for (int i = 0; i < mTabsContainer.getChildCount(); i++) {
278             View child = mTabsContainer.getChildAt(i);
279             if (child instanceof MediaItemTabView) {
280                 MediaItemTabView tabView = (MediaItemTabView) child;
281                 boolean match = mSelectedItem != null && Objects.equals(
282                         mSelectedItem.getId(),
283                         ((MediaItemMetadata) tabView.getTag()).getId());
284                 tabView.setAlpha(match ? mSelectedTabAlpha : mUnselectedTabAlpha);
285             }
286         }
287     }
288 
289     /**
290      * Updates the state of the bar.
291      */
setState(State state)292     public void setState(State state) {
293         boolean hasItems = mTabsContainer.getChildCount() > 0;
294         mState = state;
295 
296         Transition transition = new Fade().setDuration(mFadeDuration);
297         TransitionManager.beginDelayedTransition(this, transition);
298         Log.d(TAG, "Updating state: " + state + " (has items: " + hasItems + ")");
299         switch (state) {
300             case BROWSING:
301                 mNavIconContainer.setVisibility(View.GONE);
302                 mTabsContainer.setVisibility(hasItems ? View.VISIBLE : View.GONE);
303                 mTitle.setVisibility(hasItems ? View.GONE : View.VISIBLE);
304                 mAppSwitchIcon.setImageDrawable(mArrowDropDown);
305                 break;
306             case STACKED:
307                 mNavIcon.setImageDrawable(mArrowBack);
308                 mNavIconContainer.setVisibility(View.VISIBLE);
309                 mTabsContainer.setVisibility(View.GONE);
310                 mTitle.setVisibility(View.VISIBLE);
311                 mAppSwitchIcon.setImageDrawable(mArrowDropDown);
312                 break;
313             case PLAYING:
314                 mNavIcon.setImageDrawable(mCollapse);
315                 mNavIconContainer.setVisibility(hasItems || !mContentForwardEnabled ? View.GONE
316                         : View.VISIBLE);
317                 mTabsContainer.setVisibility(hasItems && mContentForwardEnabled ? View.VISIBLE
318                         : View.GONE);
319                 mTitle.setVisibility(hasItems || !mContentForwardEnabled ? View.GONE
320                         : View.VISIBLE);
321                 mAppSwitchIcon.setImageDrawable(mArrowDropDown);
322                 break;
323             case APP_SELECTION:
324                 mNavIconContainer.setVisibility(View.GONE);
325                 mTabsContainer.setVisibility(View.GONE);
326                 mTitle.setVisibility(mContentForwardEnabled ? View.VISIBLE : View.GONE);
327                 mAppSwitchIcon.setImageDrawable(mArrowDropUp);
328                 break;
329         }
330     }
331 }
332