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