1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package android.support.v17.leanback.widget;
15 
16 import android.graphics.drawable.Drawable;
17 import android.support.annotation.ColorInt;
18 import android.support.v17.leanback.R;
19 import android.support.v17.leanback.widget.ControlBarPresenter.OnControlClickedListener;
20 import android.support.v17.leanback.widget.ControlBarPresenter.OnControlSelectedListener;
21 import android.content.Context;
22 import android.graphics.Color;
23 import android.util.TypedValue;
24 import android.view.KeyEvent;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.ViewGroup.LayoutParams;
29 import android.view.ViewGroup.MarginLayoutParams;
30 import android.widget.ImageView;
31 import android.widget.LinearLayout;
32 
33 /**
34  * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
35  * series of playback control buttons. Typically this row will be the first row in a fragment
36  * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment}.
37  *
38  * <p>The detailed description is rendered using a {@link Presenter} passed in
39  * {@link #PlaybackControlsRowPresenter(Presenter)}.  Typically this will be an instance of
40  * {@link AbstractDetailsDescriptionPresenter}.  The application can access the
41  * detailed description ViewHolder from {@link ViewHolder#mDescriptionViewHolder}.
42  * </p>
43  */
44 public class PlaybackControlsRowPresenter extends RowPresenter {
45 
46     static class BoundData extends PlaybackControlsPresenter.BoundData {
47         ViewHolder mRowViewHolder;
48     }
49 
50     /**
51      * A ViewHolder for the PlaybackControlsRow.
52      */
53     public class ViewHolder extends RowPresenter.ViewHolder {
54         public final Presenter.ViewHolder mDescriptionViewHolder;
55         final ViewGroup mCard;
56         final ViewGroup mCardRightPanel;
57         final ImageView mImageView;
58         final ViewGroup mDescriptionDock;
59         final ViewGroup mControlsDock;
60         final ViewGroup mSecondaryControlsDock;
61         final View mSpacer;
62         final View mBottomSpacer;
63         View mBgView;
64         int mControlsDockMarginStart;
65         int mControlsDockMarginEnd;
66         PlaybackControlsPresenter.ViewHolder mControlsVh;
67         Presenter.ViewHolder mSecondaryControlsVh;
68         BoundData mControlsBoundData = new BoundData();
69         BoundData mSecondaryBoundData = new BoundData();
70         Presenter.ViewHolder mSelectedViewHolder;
71         Object mSelectedItem;
72         final PlaybackControlsRow.OnPlaybackStateChangedListener mListener =
73                 new PlaybackControlsRow.OnPlaybackStateChangedListener() {
74             @Override
75             public void onCurrentTimeChanged(int ms) {
76                 mPlaybackControlsPresenter.setCurrentTime(mControlsVh, ms);
77             }
78             @Override
79             public void onBufferedProgressChanged(int ms) {
80                 mPlaybackControlsPresenter.setSecondaryProgress(mControlsVh, ms);
81             }
82         };
83 
ViewHolder(View rootView, Presenter descriptionPresenter)84         ViewHolder(View rootView, Presenter descriptionPresenter) {
85             super(rootView);
86             mCard = (ViewGroup) rootView.findViewById(R.id.controls_card);
87             mCardRightPanel = (ViewGroup) rootView.findViewById(R.id.controls_card_right_panel);
88             mImageView = (ImageView) rootView.findViewById(R.id.image);
89             mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock);
90             mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock);
91             mSecondaryControlsDock =
92                     (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock);
93             mSpacer = rootView.findViewById(R.id.spacer);
94             mBottomSpacer = rootView.findViewById(R.id.bottom_spacer);
95             mDescriptionViewHolder = descriptionPresenter == null ? null :
96                     descriptionPresenter.onCreateViewHolder(mDescriptionDock);
97             if (mDescriptionViewHolder != null) {
98                 mDescriptionDock.addView(mDescriptionViewHolder.view);
99             }
100         }
101 
dispatchItemSelection()102         void dispatchItemSelection() {
103             if (!isSelected()) {
104                 return;
105             }
106             if (mSelectedViewHolder == null) {
107                 if (getOnItemViewSelectedListener() != null) {
108                     getOnItemViewSelectedListener().onItemSelected(null, null,
109                             ViewHolder.this, getRow());
110                 }
111             } else {
112                 if (getOnItemViewSelectedListener() != null) {
113                     getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, mSelectedItem,
114                             ViewHolder.this, getRow());
115                 }
116             }
117         };
118 
getPresenter(boolean primary)119         Presenter getPresenter(boolean primary) {
120             ObjectAdapter adapter = primary ?
121                     ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() :
122                             ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
123             if (adapter == null) {
124                 return null;
125             }
126             if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
127                 ControlButtonPresenterSelector selector =
128                         (ControlButtonPresenterSelector) adapter.getPresenterSelector();
129                 return primary ? selector.getPrimaryPresenter() :
130                     selector.getSecondaryPresenter();
131             }
132             return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null);
133         }
134 
setOutline(View view)135         void setOutline(View view) {
136             if (mBgView != null) {
137                 RoundedRectHelper.getInstance().setClipToRoundedOutline(mBgView, false);
138                 ShadowHelper.getInstance().setZ(mBgView, 0);
139             }
140             mBgView = view;
141             RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true);
142 
143             if (sShadowZ == 0) {
144                 sShadowZ = view.getResources().getDimensionPixelSize(
145                         R.dimen.lb_playback_controls_z);
146             }
147             ShadowHelper.getInstance().setZ(view, sShadowZ);
148         }
149     }
150 
151     private int mBackgroundColor = Color.TRANSPARENT;
152     private boolean mBackgroundColorSet;
153     private int mProgressColor = Color.TRANSPARENT;
154     private boolean mProgressColorSet;
155     private boolean mSecondaryActionsHidden;
156     private Presenter mDescriptionPresenter;
157     private PlaybackControlsPresenter mPlaybackControlsPresenter;
158     private ControlBarPresenter mSecondaryControlsPresenter;
159     private OnActionClickedListener mOnActionClickedListener;
160     private static float sShadowZ;
161 
162     private final OnControlSelectedListener mOnControlSelectedListener =
163             new OnControlSelectedListener() {
164         @Override
165         public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item,
166                 ControlBarPresenter.BoundData data) {
167             ViewHolder vh = ((BoundData) data).mRowViewHolder;
168             if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) {
169                 vh.mSelectedViewHolder = itemViewHolder;
170                 vh.mSelectedItem = item;
171                 vh.dispatchItemSelection();
172             }
173         }
174     };
175 
176     private final OnControlClickedListener mOnControlClickedListener =
177             new OnControlClickedListener() {
178         @Override
179         public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item,
180                 ControlBarPresenter.BoundData data) {
181             ViewHolder vh = ((BoundData) data).mRowViewHolder;
182             if (vh.getOnItemViewClickedListener() != null) {
183                 vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
184                         vh, vh.getRow());
185             }
186             if (mOnActionClickedListener != null && item instanceof Action) {
187                 mOnActionClickedListener.onActionClicked((Action) item);
188             }
189         }
190     };
191 
192     /**
193      * Constructor for a PlaybackControlsRowPresenter.
194      *
195      * @param descriptionPresenter Presenter for displaying item details.
196      */
PlaybackControlsRowPresenter(Presenter descriptionPresenter)197     public PlaybackControlsRowPresenter(Presenter descriptionPresenter) {
198         setHeaderPresenter(null);
199         setSelectEffectEnabled(false);
200 
201         mDescriptionPresenter = descriptionPresenter;
202         mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls);
203         mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar);
204 
205         mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
206         mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
207         mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
208         mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
209     }
210 
211     /**
212      * Constructor for a PlaybackControlsRowPresenter.
213      */
PlaybackControlsRowPresenter()214     public PlaybackControlsRowPresenter() {
215         this(null);
216     }
217 
218     /**
219      * Sets the listener for {@link Action} click events.
220      */
setOnActionClickedListener(OnActionClickedListener listener)221     public void setOnActionClickedListener(OnActionClickedListener listener) {
222         mOnActionClickedListener = listener;
223     }
224 
225     /**
226      * Returns the listener for {@link Action} click events.
227      */
getOnActionClickedListener()228     public OnActionClickedListener getOnActionClickedListener() {
229         return mOnActionClickedListener;
230     }
231 
232     /**
233      * Sets the background color.  If not set, a default from the theme will be used.
234      */
setBackgroundColor(@olorInt int color)235     public void setBackgroundColor(@ColorInt int color) {
236         mBackgroundColor = color;
237         mBackgroundColorSet = true;
238     }
239 
240     /**
241      * Returns the background color.  If no background color was set, transparent
242      * is returned.
243      */
244     @ColorInt
getBackgroundColor()245     public int getBackgroundColor() {
246         return mBackgroundColor;
247     }
248 
249     /**
250      * Sets the primary color for the progress bar.  If not set, a default from
251      * the theme will be used.
252      */
setProgressColor(@olorInt int color)253     public void setProgressColor(@ColorInt int color) {
254         mProgressColor = color;
255         mProgressColorSet = true;
256     }
257 
258     /**
259      * Returns the primary color for the progress bar.  If no color was set, transparent
260      * is returned.
261      */
262     @ColorInt
getProgressColor()263     public int getProgressColor() {
264         return mProgressColor;
265     }
266 
267     /**
268      * Sets the secondary actions to be hidden behind a "more actions" button.
269      * When "more actions" is selected, the primary actions are replaced with
270      * the secondary actions.
271      */
setSecondaryActionsHidden(boolean hidden)272     public void setSecondaryActionsHidden(boolean hidden) {
273         mSecondaryActionsHidden = hidden;
274     }
275 
276     /**
277      * Returns true if secondary actions are hidden.
278      */
areSecondaryActionsHidden()279     public boolean areSecondaryActionsHidden() {
280         return mSecondaryActionsHidden;
281     }
282 
283     /**
284      * Shows or hides space at the bottom of the playback controls row.
285      * This allows the row to hug the bottom of the display when no
286      * other rows are present.
287      */
showBottomSpace(ViewHolder vh, boolean show)288     public void showBottomSpace(ViewHolder vh, boolean show) {
289         vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE);
290     }
291 
292     /**
293      * Displays the primary actions.  This will override the user having selected "more actions"
294      * to display the secondary actions; see {@link #setSecondaryActionsHidden(boolean)}.
295      */
showPrimaryActions(ViewHolder vh)296     public void showPrimaryActions(ViewHolder vh) {
297         mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh);
298         mPlaybackControlsPresenter.resetFocus(vh.mControlsVh);
299     }
300 
getDefaultBackgroundColor(Context context)301     private int getDefaultBackgroundColor(Context context) {
302         TypedValue outValue = new TypedValue();
303         if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
304             return context.getResources().getColor(outValue.resourceId);
305         }
306         return context.getResources().getColor(R.color.lb_default_brand_color);
307     }
308 
getDefaultProgressColor(Context context)309     private int getDefaultProgressColor(Context context) {
310         TypedValue outValue = new TypedValue();
311         if (context.getTheme()
312                 .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) {
313             return context.getResources().getColor(outValue.resourceId);
314         }
315         return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme);
316     }
317 
318     @Override
createRowViewHolder(ViewGroup parent)319     protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
320         View v = LayoutInflater.from(parent.getContext())
321             .inflate(R.layout.lb_playback_controls_row, parent, false);
322         ViewHolder vh = new ViewHolder(v, mDescriptionPresenter);
323         initRow(vh);
324         return vh;
325     }
326 
initRow(final ViewHolder vh)327     private void initRow(final ViewHolder vh) {
328         MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
329         vh.mControlsDockMarginStart = lp.getMarginStart();
330         vh.mControlsDockMarginEnd = lp.getMarginEnd();
331 
332         vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder)
333                 mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock);
334         mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh, mProgressColorSet ?
335                 mProgressColor : getDefaultProgressColor(vh.mControlsDock.getContext()));
336         mPlaybackControlsPresenter.setBackgroundColor(vh.mControlsVh, mBackgroundColorSet ?
337                 mBackgroundColor : getDefaultBackgroundColor(vh.view.getContext()));
338         vh.mControlsDock.addView(vh.mControlsVh.view);
339 
340         vh.mSecondaryControlsVh =
341                 mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock);
342         if (!mSecondaryActionsHidden) {
343             vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
344         }
345         ((PlaybackControlsRowView) vh.view).setOnUnhandledKeyListener(
346                 new PlaybackControlsRowView.OnUnhandledKeyListener() {
347             @Override
348             public boolean onUnhandledKey(KeyEvent event) {
349                 if (vh.getOnKeyListener() != null) {
350                     if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
351                         return true;
352                     }
353                 }
354                 return false;
355             }
356         });
357     }
358 
359     @Override
onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)360     protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
361         super.onBindRowViewHolder(holder, item);
362 
363         ViewHolder vh = (ViewHolder) holder;
364         PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
365 
366         mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden);
367 
368         if (row.getItem() == null) {
369             vh.mDescriptionDock.setVisibility(View.GONE);
370             vh.mSpacer.setVisibility(View.GONE);
371         } else {
372             vh.mDescriptionDock.setVisibility(View.VISIBLE);
373             if (vh.mDescriptionViewHolder != null) {
374                 mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem());
375             }
376             vh.mSpacer.setVisibility(View.VISIBLE);
377         }
378 
379         if (row.getImageDrawable() == null || row.getItem() == null) {
380             vh.mImageView.setImageDrawable(null);
381             updateCardLayout(vh, LayoutParams.WRAP_CONTENT);
382         } else {
383             vh.mImageView.setImageDrawable(row.getImageDrawable());
384             updateCardLayout(vh, vh.mImageView.getLayoutParams().height);
385         }
386 
387         vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter();
388         vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter();
389         vh.mControlsBoundData.presenter = vh.getPresenter(true);
390         vh.mControlsBoundData.mRowViewHolder = vh;
391         mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData);
392 
393         vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter();
394         vh.mSecondaryBoundData.presenter = vh.getPresenter(false);
395         vh.mSecondaryBoundData.mRowViewHolder = vh;
396         mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh,
397                 vh.mSecondaryBoundData);
398 
399         mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime());
400         mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime());
401         mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress());
402         row.setOnPlaybackStateChangedListener(vh.mListener);
403     }
404 
updateCardLayout(ViewHolder vh, int height)405     private void updateCardLayout(ViewHolder vh, int height) {
406         LayoutParams lp = vh.mCardRightPanel.getLayoutParams();
407         lp.height = height;
408         vh.mCardRightPanel.setLayoutParams(lp);
409 
410         MarginLayoutParams mlp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
411         LinearLayout.LayoutParams llp =
412                 (LinearLayout.LayoutParams) vh.mDescriptionDock.getLayoutParams();
413 
414         if (height == LayoutParams.WRAP_CONTENT) {
415             llp.height = LayoutParams.WRAP_CONTENT;
416             mlp.setMarginStart(0);
417             mlp.setMarginEnd(0);
418             vh.mCard.setBackground(null);
419             vh.setOutline(vh.mControlsDock);
420             mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true);
421         } else {
422             llp.height = 0;
423             llp.weight = 1;
424             mlp.setMarginStart(vh.mControlsDockMarginStart);
425             mlp.setMarginEnd(vh.mControlsDockMarginEnd);
426             vh.mCard.setBackgroundColor(mBackgroundColorSet ? mBackgroundColor :
427                     getDefaultBackgroundColor(vh.mCard.getContext()));
428             vh.setOutline(vh.mCard);
429             mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false);
430         }
431         vh.mDescriptionDock.setLayoutParams(llp);
432         vh.mControlsDock.setLayoutParams(mlp);
433     }
434 
435     @Override
onUnbindRowViewHolder(RowPresenter.ViewHolder holder)436     protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
437         ViewHolder vh = (ViewHolder) holder;
438         PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
439 
440         if (vh.mDescriptionViewHolder != null) {
441             mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder);
442         }
443         mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh);
444         mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh);
445         row.setOnPlaybackStateChangedListener(null);
446 
447         super.onUnbindRowViewHolder(holder);
448     }
449 
450     @Override
onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected)451     protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
452         super.onRowViewSelected(vh, selected);
453         if (selected) {
454             ((ViewHolder) vh).dispatchItemSelection();
455         }
456     }
457 
458     @Override
onRowViewAttachedToWindow(RowPresenter.ViewHolder vh)459     protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
460         super.onRowViewAttachedToWindow(vh);
461         if (mDescriptionPresenter != null) {
462             mDescriptionPresenter.onViewAttachedToWindow(
463                     ((ViewHolder) vh).mDescriptionViewHolder);
464         }
465     }
466 
467     @Override
onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh)468     protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
469         super.onRowViewDetachedFromWindow(vh);
470         if (mDescriptionPresenter != null) {
471             mDescriptionPresenter.onViewDetachedFromWindow(
472                     ((ViewHolder) vh).mDescriptionViewHolder);
473         }
474     }
475 }
476