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