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.drawable.ClipDrawable;
18 import android.graphics.drawable.ColorDrawable;
19 import android.graphics.drawable.Drawable;
20 import android.graphics.drawable.LayerDrawable;
21 import android.view.Gravity;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.ViewGroup.MarginLayoutParams;
26 import android.widget.FrameLayout;
27 import android.widget.ProgressBar;
28 import android.widget.TextView;
29 
30 import androidx.annotation.ColorInt;
31 import androidx.leanback.R;
32 import androidx.leanback.util.MathUtil;
33 
34 /**
35  * A presenter for a control bar that supports "more actions",
36  * and toggling the set of controls between primary and secondary
37  * sets of {@link Actions}.
38  */
39 class PlaybackControlsPresenter extends ControlBarPresenter {
40 
41     /**
42      * The data type expected by this presenter.
43      */
44     static class BoundData extends ControlBarPresenter.BoundData {
45         /**
46          * The adapter containing secondary actions.
47          */
48         ObjectAdapter secondaryActionsAdapter;
49     }
50 
51     class ViewHolder extends ControlBarPresenter.ViewHolder {
52         ObjectAdapter mMoreActionsAdapter;
53         ObjectAdapter.DataObserver mMoreActionsObserver;
54         final FrameLayout mMoreActionsDock;
55         Presenter.ViewHolder mMoreActionsViewHolder;
56         boolean mMoreActionsShowing;
57         final TextView mCurrentTime;
58         final TextView mTotalTime;
59         final ProgressBar mProgressBar;
60         long mCurrentTimeInMs = -1;         // Hold current time in milliseconds
61         long mTotalTimeInMs = -1;           // Hold total time in milliseconds
62         long mSecondaryProgressInMs = -1;   // Hold secondary progress in milliseconds
63         StringBuilder mTotalTimeStringBuilder = new StringBuilder();
64         StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
65         int mCurrentTimeMarginStart;
66         int mTotalTimeMarginEnd;
67 
ViewHolder(View rootView)68         ViewHolder(View rootView) {
69             super(rootView);
70             mMoreActionsDock = (FrameLayout) rootView.findViewById(R.id.more_actions_dock);
71             mCurrentTime = (TextView) rootView.findViewById(R.id.current_time);
72             mTotalTime = (TextView) rootView.findViewById(R.id.total_time);
73             mProgressBar = (ProgressBar) rootView.findViewById(R.id.playback_progress);
74             mMoreActionsObserver = new ObjectAdapter.DataObserver() {
75                 @Override
76                 public void onChanged() {
77                     if (mMoreActionsShowing) {
78                         showControls(mPresenter);
79                     }
80                 }
81                 @Override
82                 public void onItemRangeChanged(int positionStart, int itemCount) {
83                     if (mMoreActionsShowing) {
84                         for (int i = 0; i < itemCount; i++) {
85                             bindControlToAction(positionStart + i, mPresenter);
86                         }
87                     }
88                 }
89             };
90             mCurrentTimeMarginStart =
91                     ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
92             mTotalTimeMarginEnd =
93                     ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
94         }
95 
showMoreActions(boolean show)96         void showMoreActions(boolean show) {
97             if (show) {
98                 if (mMoreActionsViewHolder == null) {
99                     Action action = new PlaybackControlsRow.MoreActions(mMoreActionsDock.getContext());
100                     mMoreActionsViewHolder = mPresenter.onCreateViewHolder(mMoreActionsDock);
101                     mPresenter.onBindViewHolder(mMoreActionsViewHolder, action);
102                     mPresenter.setOnClickListener(mMoreActionsViewHolder, new View.OnClickListener() {
103                         @Override
104                         public void onClick(View v) {
105                             toggleMoreActions();
106                         }
107                     });
108                 }
109                 if (mMoreActionsViewHolder.view.getParent() == null) {
110                     mMoreActionsDock.addView(mMoreActionsViewHolder.view);
111                 }
112             } else if (mMoreActionsViewHolder != null
113                     && mMoreActionsViewHolder.view.getParent() != null) {
114                 mMoreActionsDock.removeView(mMoreActionsViewHolder.view);
115             }
116         }
117 
toggleMoreActions()118         void toggleMoreActions() {
119             mMoreActionsShowing = !mMoreActionsShowing;
120             showControls(mPresenter);
121         }
122 
123         @Override
getDisplayedAdapter()124         ObjectAdapter getDisplayedAdapter() {
125             return mMoreActionsShowing ? mMoreActionsAdapter : mAdapter;
126         }
127 
128         @Override
getChildMarginFromCenter(Context context, int numControls)129         int getChildMarginFromCenter(Context context, int numControls) {
130             int margin = getControlIconWidth(context);
131             if (numControls < 4) {
132                 margin += getChildMarginBiggest(context);
133             } else if (numControls < 6) {
134                 margin += getChildMarginBigger(context);
135             } else {
136                 margin += getChildMarginDefault(context);
137             }
138             return margin;
139         }
140 
setTotalTime(long totalTimeMs)141         void setTotalTime(long totalTimeMs) {
142             if (totalTimeMs <= 0) {
143                 mTotalTime.setVisibility(View.GONE);
144                 mProgressBar.setVisibility(View.GONE);
145             } else {
146                 mTotalTime.setVisibility(View.VISIBLE);
147                 mProgressBar.setVisibility(View.VISIBLE);
148                 mTotalTimeInMs = totalTimeMs;
149                 formatTime(totalTimeMs / 1000, mTotalTimeStringBuilder);
150                 mTotalTime.setText(mTotalTimeStringBuilder.toString());
151                 mProgressBar.setMax(Integer.MAX_VALUE);//current progress will be a fraction of this
152             }
153         }
154 
getTotalTime()155         long getTotalTime() {
156             return mTotalTimeInMs;
157         }
158 
setCurrentTime(long currentTimeMs)159         void setCurrentTime(long currentTimeMs) {
160             long seconds = currentTimeMs / 1000;
161             if (currentTimeMs != mCurrentTimeInMs) {
162                 mCurrentTimeInMs = currentTimeMs;
163                 formatTime(seconds, mCurrentTimeStringBuilder);
164                 mCurrentTime.setText(mCurrentTimeStringBuilder.toString());
165             }
166             // Use ratio to represent current progres
167             double ratio = (double) mCurrentTimeInMs / mTotalTimeInMs;     // Range: [0, 1]
168             double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
169             mProgressBar.setProgress((int)progressRatio);
170         }
171 
getCurrentTime()172         long getCurrentTime() {
173             return mTotalTimeInMs;
174         }
175 
setSecondaryProgress(long progressMs)176         void setSecondaryProgress(long progressMs) {
177             mSecondaryProgressInMs = progressMs;
178             // Solve the progress bar by using ratio
179             double ratio = (double) progressMs / mTotalTimeInMs;           // Range: [0, 1]
180             double progressRatio = ratio * Integer.MAX_VALUE;   // Could safely cast to int
181             mProgressBar.setSecondaryProgress((int) progressRatio);
182         }
183 
getSecondaryProgress()184         long getSecondaryProgress() {
185             return mSecondaryProgressInMs;
186         }
187     }
188 
formatTime(long seconds, StringBuilder sb)189     static void formatTime(long seconds, StringBuilder sb) {
190         long minutes = seconds / 60;
191         long hours = minutes / 60;
192         seconds -= minutes * 60;
193         minutes -= hours * 60;
194 
195         sb.setLength(0);
196         if (hours > 0) {
197             sb.append(hours).append(':');
198             if (minutes < 10) {
199                 sb.append('0');
200             }
201         }
202         sb.append(minutes).append(':');
203         if (seconds < 10) {
204             sb.append('0');
205         }
206         sb.append(seconds);
207     }
208 
209     private boolean mMoreActionsEnabled = true;
210     private static int sChildMarginBigger;
211     private static int sChildMarginBiggest;
212 
213     /**
214      * Constructor for a PlaybackControlsRowPresenter.
215      *
216      * @param layoutResourceId The resource id of the layout for this presenter.
217      */
PlaybackControlsPresenter(int layoutResourceId)218     public PlaybackControlsPresenter(int layoutResourceId) {
219         super(layoutResourceId);
220     }
221 
222     /**
223      * Enables the display of secondary actions.
224      * A "more actions" button will be displayed.  When "more actions" is selected,
225      * the primary actions are replaced with the secondary actions.
226      */
enableSecondaryActions(boolean enable)227     public void enableSecondaryActions(boolean enable) {
228         mMoreActionsEnabled = enable;
229     }
230 
231     /**
232      * Returns true if secondary actions are enabled.
233      */
areMoreActionsEnabled()234     public boolean areMoreActionsEnabled() {
235         return mMoreActionsEnabled;
236     }
237 
setProgressColor(ViewHolder vh, @ColorInt int color)238     public void setProgressColor(ViewHolder vh, @ColorInt int color) {
239         Drawable drawable = new ClipDrawable(new ColorDrawable(color),
240                 Gravity.LEFT, ClipDrawable.HORIZONTAL);
241         ((LayerDrawable) vh.mProgressBar.getProgressDrawable())
242                 .setDrawableByLayerId(android.R.id.progress, drawable);
243     }
244 
setTotalTime(ViewHolder vh, int ms)245     public void setTotalTime(ViewHolder vh, int ms) {
246         setTotalTimeLong(vh, (long) ms);
247     }
248 
setTotalTimeLong(ViewHolder vh, long ms)249     public void setTotalTimeLong(ViewHolder vh, long ms) {
250         vh.setTotalTime(ms);
251     }
252 
getTotalTime(ViewHolder vh)253     public int getTotalTime(ViewHolder vh) {
254         return MathUtil.safeLongToInt(getTotalTimeLong(vh));
255     }
256 
getTotalTimeLong(ViewHolder vh)257     public long getTotalTimeLong(ViewHolder vh) {
258         return vh.getTotalTime();
259     }
260 
setCurrentTime(ViewHolder vh, int ms)261     public void setCurrentTime(ViewHolder vh, int ms) {
262         setCurrentTimeLong(vh, (long) ms);
263     }
264 
setCurrentTimeLong(ViewHolder vh, long ms)265     public void setCurrentTimeLong(ViewHolder vh, long ms) {
266         vh.setCurrentTime(ms);
267     }
268 
getCurrentTime(ViewHolder vh)269     public int getCurrentTime(ViewHolder vh) {
270         return MathUtil.safeLongToInt(getCurrentTimeLong(vh));
271     }
272 
getCurrentTimeLong(ViewHolder vh)273     public long getCurrentTimeLong(ViewHolder vh) {
274         return vh.getCurrentTime();
275     }
276 
setSecondaryProgress(ViewHolder vh, int progressMs)277     public void setSecondaryProgress(ViewHolder vh, int progressMs) {
278         setSecondaryProgressLong(vh, (long) progressMs);
279     }
280 
setSecondaryProgressLong(ViewHolder vh, long progressMs)281     public void setSecondaryProgressLong(ViewHolder vh, long progressMs) {
282         vh.setSecondaryProgress(progressMs);
283     }
284 
getSecondaryProgress(ViewHolder vh)285     public int getSecondaryProgress(ViewHolder vh) {
286         return MathUtil.safeLongToInt(getSecondaryProgressLong(vh));
287     }
288 
getSecondaryProgressLong(ViewHolder vh)289     public long getSecondaryProgressLong(ViewHolder vh) {
290         return vh.getSecondaryProgress();
291     }
292 
showPrimaryActions(ViewHolder vh)293     public void showPrimaryActions(ViewHolder vh) {
294         if (vh.mMoreActionsShowing) {
295             vh.toggleMoreActions();
296         }
297     }
298 
resetFocus(ViewHolder vh)299     public void resetFocus(ViewHolder vh) {
300         vh.mControlBar.requestFocus();
301     }
302 
enableTimeMargins(ViewHolder vh, boolean enable)303     public void enableTimeMargins(ViewHolder vh, boolean enable) {
304         MarginLayoutParams lp;
305         lp = (MarginLayoutParams) vh.mCurrentTime.getLayoutParams();
306         lp.setMarginStart(enable ? vh.mCurrentTimeMarginStart : 0);
307         vh.mCurrentTime.setLayoutParams(lp);
308 
309         lp = (MarginLayoutParams) vh.mTotalTime.getLayoutParams();
310         lp.setMarginEnd(enable ? vh.mTotalTimeMarginEnd : 0);
311         vh.mTotalTime.setLayoutParams(lp);
312     }
313 
314     @Override
onCreateViewHolder(ViewGroup parent)315     public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
316         View v = LayoutInflater.from(parent.getContext())
317             .inflate(getLayoutResourceId(), parent, false);
318         return new ViewHolder(v);
319     }
320 
321     @Override
onBindViewHolder(Presenter.ViewHolder holder, Object item)322     public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
323         ViewHolder vh = (ViewHolder) holder;
324         BoundData data = (BoundData) item;
325 
326         // If binding to a new adapter, display primary actions.
327         if (vh.mMoreActionsAdapter != data.secondaryActionsAdapter) {
328             vh.mMoreActionsAdapter = data.secondaryActionsAdapter;
329             vh.mMoreActionsAdapter.registerObserver(vh.mMoreActionsObserver);
330             vh.mMoreActionsShowing = false;
331         }
332 
333         super.onBindViewHolder(holder, item);
334         vh.showMoreActions(mMoreActionsEnabled);
335     }
336 
337     @Override
onUnbindViewHolder(Presenter.ViewHolder holder)338     public void onUnbindViewHolder(Presenter.ViewHolder holder) {
339         super.onUnbindViewHolder(holder);
340         ViewHolder vh = (ViewHolder) holder;
341         if (vh.mMoreActionsAdapter != null) {
342             vh.mMoreActionsAdapter.unregisterObserver(vh.mMoreActionsObserver);
343             vh.mMoreActionsAdapter = null;
344         }
345     }
346 
getChildMarginBigger(Context context)347     int getChildMarginBigger(Context context) {
348         if (sChildMarginBigger == 0) {
349             sChildMarginBigger = context.getResources().getDimensionPixelSize(
350                     R.dimen.lb_playback_controls_child_margin_bigger);
351         }
352         return sChildMarginBigger;
353     }
354 
getChildMarginBiggest(Context context)355     int getChildMarginBiggest(Context context) {
356         if (sChildMarginBiggest == 0) {
357             sChildMarginBiggest = context.getResources().getDimensionPixelSize(
358                     R.dimen.lb_playback_controls_child_margin_biggest);
359         }
360         return sChildMarginBiggest;
361     }
362 }
363