Home | History | Annotate | Download | only in widgets
      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          */
     68         void onTabSelected(MediaItemMetadata item);
     69 
     70         /**
     71          * Invoked when the user clicks on the back button
     72          */
     73         void onBack();
     74 
     75         /**
     76          * Invoked when the user clicks on the collapse button
     77          */
     78         void onCollapse();
     79 
     80         /**
     81          * Invoked when the user clicks on the app selection switch
     82          */
     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 
    113     public AppBarView(Context context) {
    114         this(context, null);
    115     }
    116 
    117     public AppBarView(Context context, AttributeSet attrs) {
    118         this(context, attrs, 0);
    119     }
    120 
    121     public AppBarView(Context context, AttributeSet attrs, int defStyleAttr) {
    122         this(context, attrs, defStyleAttr, 0);
    123     }
    124 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    262     public void setAppSelection(boolean enabled) {
    263         mAppSwitchIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
    264     }
    265 
    266     /**
    267      * Updates the currently active item
    268      */
    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 
    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      */
    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