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