1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.media.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.Point; 24 import android.os.Bundle; 25 import android.support.v4.media.MediaMetadataCompat; 26 import android.support.v4.media.session.MediaControllerCompat; 27 import android.support.v4.media.session.PlaybackStateCompat; 28 import android.util.AttributeSet; 29 import android.view.Gravity; 30 import android.view.KeyEvent; 31 import android.view.LayoutInflater; 32 import android.view.MotionEvent; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.WindowManager; 36 import android.widget.AdapterView; 37 import android.widget.BaseAdapter; 38 import android.widget.ImageButton; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.ListView; 42 import android.widget.PopupWindow; 43 import android.widget.ProgressBar; 44 import android.widget.RelativeLayout; 45 import android.widget.SeekBar; 46 import android.widget.SeekBar.OnSeekBarChangeListener; 47 import android.widget.TextView; 48 49 import androidx.annotation.IntDef; 50 import androidx.annotation.NonNull; 51 import androidx.annotation.Nullable; 52 import androidx.annotation.RequiresApi; 53 import androidx.annotation.RestrictTo; 54 import androidx.media.SessionToken2; 55 import androidx.mediarouter.app.MediaRouteButton; 56 import androidx.mediarouter.media.MediaRouteSelector; 57 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Formatter; 63 import java.util.List; 64 import java.util.Locale; 65 66 /** 67 * A View that contains the controls for {@link android.media.MediaPlayer}. 68 * It provides a wide range of buttons that serve the following functions: play/pause, 69 * rewind/fast-forward, skip to next/previous, select subtitle track, enter/exit full screen mode, 70 * adjust video quality, select audio track, mute/unmute, and adjust playback speed. 71 * 72 * <p> 73 * <em> MediaControlView2 can be initialized in two different ways: </em> 74 * 1) When initializing {@link VideoView2} a default MediaControlView2 is created. 75 * 2) Initialize MediaControlView2 programmatically and add it to a {@link ViewGroup} instance. 76 * 77 * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController, 78 * which is necessary to communicate with MediaSession. In the second option, however, the 79 * developer needs to manually retrieve a MediaController instance from MediaSession and set it to 80 * MediaControlView2. 81 * 82 * <p> 83 * There is no separate method that handles the show/hide behavior for MediaControlView2. Instead, 84 * one can directly change the visibility of this view by calling {@link View#setVisibility(int)}. 85 * The values supported are View.VISIBLE and View.GONE. 86 * 87 * <p> 88 * In addition, the following customizations are supported: 89 * 1) Set focus to the play/pause button by calling requestPlayButtonFocus(). 90 * 2) Set full screen mode 91 * 92 */ 93 @RequiresApi(21) // TODO correct minSdk API use incompatibilities and remove before release. 94 public class MediaControlView2 extends BaseLayout { 95 /** 96 * @hide 97 */ 98 @RestrictTo(LIBRARY_GROUP) 99 @IntDef({ 100 BUTTON_PLAY_PAUSE, 101 BUTTON_FFWD, 102 BUTTON_REW, 103 BUTTON_NEXT, 104 BUTTON_PREV, 105 BUTTON_SUBTITLE, 106 BUTTON_FULL_SCREEN, 107 BUTTON_OVERFLOW, 108 BUTTON_MUTE, 109 BUTTON_ASPECT_RATIO, 110 BUTTON_SETTINGS 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface Button {} 114 115 /** 116 * MediaControlView2 button value for playing and pausing media. 117 * @hide 118 */ 119 @RestrictTo(LIBRARY_GROUP) 120 public static final int BUTTON_PLAY_PAUSE = 1; 121 /** 122 * MediaControlView2 button value for jumping 30 seconds forward. 123 * @hide 124 */ 125 @RestrictTo(LIBRARY_GROUP) 126 public static final int BUTTON_FFWD = 2; 127 /** 128 * MediaControlView2 button value for jumping 10 seconds backward. 129 * @hide 130 */ 131 @RestrictTo(LIBRARY_GROUP) 132 public static final int BUTTON_REW = 3; 133 /** 134 * MediaControlView2 button value for jumping to next media. 135 * @hide 136 */ 137 @RestrictTo(LIBRARY_GROUP) 138 public static final int BUTTON_NEXT = 4; 139 /** 140 * MediaControlView2 button value for jumping to previous media. 141 * @hide 142 */ 143 @RestrictTo(LIBRARY_GROUP) 144 public static final int BUTTON_PREV = 5; 145 /** 146 * MediaControlView2 button value for showing/hiding subtitle track. 147 * @hide 148 */ 149 @RestrictTo(LIBRARY_GROUP) 150 public static final int BUTTON_SUBTITLE = 6; 151 /** 152 * MediaControlView2 button value for toggling full screen. 153 * @hide 154 */ 155 @RestrictTo(LIBRARY_GROUP) 156 public static final int BUTTON_FULL_SCREEN = 7; 157 /** 158 * MediaControlView2 button value for showing/hiding overflow buttons. 159 * @hide 160 */ 161 @RestrictTo(LIBRARY_GROUP) 162 public static final int BUTTON_OVERFLOW = 8; 163 /** 164 * MediaControlView2 button value for muting audio. 165 * @hide 166 */ 167 @RestrictTo(LIBRARY_GROUP) 168 public static final int BUTTON_MUTE = 9; 169 /** 170 * MediaControlView2 button value for adjusting aspect ratio of view. 171 * @hide 172 */ 173 @RestrictTo(LIBRARY_GROUP) 174 public static final int BUTTON_ASPECT_RATIO = 10; 175 /** 176 * MediaControlView2 button value for showing/hiding settings page. 177 * @hide 178 */ 179 @RestrictTo(LIBRARY_GROUP) 180 public static final int BUTTON_SETTINGS = 11; 181 182 private static final String TAG = "MediaControlView2"; 183 184 static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount"; 185 static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount"; 186 static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount"; 187 static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed"; 188 static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex"; 189 static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex"; 190 static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus"; 191 static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement"; 192 static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus"; 193 194 // String for sending command to show subtitle to MediaSession. 195 static final String COMMAND_SHOW_SUBTITLE = "showSubtitle"; 196 // String for sending command to hide subtitle to MediaSession. 197 static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle"; 198 // String for sending command to select audio track to MediaSession. 199 static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack"; 200 // String for sending command to set playback speed to MediaSession. 201 static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed"; 202 // String for sending command to mute audio to MediaSession. 203 static final String COMMAND_MUTE = "Mute"; 204 // String for sending command to unmute audio to MediaSession. 205 static final String COMMAND_UNMUTE = "Unmute"; 206 207 private static final int SETTINGS_MODE_AUDIO_TRACK = 0; 208 private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1; 209 private static final int SETTINGS_MODE_HELP = 2; 210 private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3; 211 private static final int SETTINGS_MODE_VIDEO_QUALITY = 4; 212 private static final int SETTINGS_MODE_MAIN = 5; 213 private static final int PLAYBACK_SPEED_1x_INDEX = 3; 214 215 private static final int MEDIA_TYPE_DEFAULT = 0; 216 private static final int MEDIA_TYPE_MUSIC = 1; 217 private static final int MEDIA_TYPE_ADVERTISEMENT = 2; 218 219 private static final int SIZE_TYPE_EMBEDDED = 0; 220 private static final int SIZE_TYPE_FULL = 1; 221 private static final int SIZE_TYPE_MINIMAL = 2; 222 223 private static final int MAX_PROGRESS = 1000; 224 private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000; 225 private static final int REWIND_TIME_MS = 10000; 226 private static final int FORWARD_TIME_MS = 30000; 227 private static final int AD_SKIP_WAIT_TIME_MS = 5000; 228 private static final int RESOURCE_NON_EXISTENT = -1; 229 private static final String RESOURCE_EMPTY = ""; 230 231 private Resources mResources; 232 private MediaControllerCompat mController; 233 private MediaControllerCompat.TransportControls mControls; 234 private PlaybackStateCompat mPlaybackState; 235 private MediaMetadataCompat mMetadata; 236 private OnFullScreenListener mOnFullScreenListener; 237 private int mDuration; 238 private int mPrevState; 239 private int mPrevWidth; 240 private int mPrevHeight; 241 private int mOriginalLeftBarWidth; 242 private int mVideoTrackCount; 243 private int mAudioTrackCount; 244 private int mSubtitleTrackCount; 245 private int mSettingsMode; 246 private int mSelectedSubtitleTrackIndex; 247 private int mSelectedAudioTrackIndex; 248 private int mSelectedVideoQualityIndex; 249 private int mSelectedSpeedIndex; 250 private int mEmbeddedSettingsItemWidth; 251 private int mFullSettingsItemWidth; 252 private int mSettingsItemHeight; 253 private int mSettingsWindowMargin; 254 private int mMediaType; 255 private int mSizeType; 256 private int mOrientation; 257 private long mPlaybackActions; 258 private boolean mDragging; 259 private boolean mIsFullScreen; 260 private boolean mOverflowExpanded; 261 private boolean mIsStopped; 262 private boolean mSubtitleIsEnabled; 263 private boolean mSeekAvailable; 264 private boolean mIsAdvertisement; 265 private boolean mIsMute; 266 private boolean mNeedUXUpdate; 267 268 // Relating to Title Bar View 269 private ViewGroup mRoot; 270 private View mTitleBar; 271 private TextView mTitleView; 272 private View mAdExternalLink; 273 private ImageButton mBackButton; 274 private MediaRouteButton mRouteButton; 275 private MediaRouteSelector mRouteSelector; 276 277 // Relating to Center View 278 private ViewGroup mCenterView; 279 private View mTransportControls; 280 private ImageButton mPlayPauseButton; 281 private ImageButton mFfwdButton; 282 private ImageButton mRewButton; 283 private ImageButton mNextButton; 284 private ImageButton mPrevButton; 285 286 // Relating to Minimal Extra View 287 private LinearLayout mMinimalExtraView; 288 289 // Relating to Progress Bar View 290 private ProgressBar mProgress; 291 private View mProgressBuffer; 292 293 // Relating to Bottom Bar View 294 private ViewGroup mBottomBar; 295 296 // Relating to Bottom Bar Left View 297 private ViewGroup mBottomBarLeftView; 298 private ViewGroup mTimeView; 299 private TextView mEndTime; 300 private TextView mCurrentTime; 301 private TextView mAdSkipView; 302 private StringBuilder mFormatBuilder; 303 private Formatter mFormatter; 304 305 // Relating to Bottom Bar Right View 306 private ViewGroup mBottomBarRightView; 307 private ViewGroup mBasicControls; 308 private ViewGroup mExtraControls; 309 private ViewGroup mCustomButtons; 310 private ImageButton mSubtitleButton; 311 private ImageButton mFullScreenButton; 312 private ImageButton mOverflowButtonRight; 313 private ImageButton mOverflowButtonLeft; 314 private ImageButton mMuteButton; 315 private ImageButton mVideoQualityButton; 316 private ImageButton mSettingsButton; 317 private TextView mAdRemainingView; 318 319 // Relating to Settings List View 320 private ListView mSettingsListView; 321 private PopupWindow mSettingsWindow; 322 private SettingsAdapter mSettingsAdapter; 323 private SubSettingsAdapter mSubSettingsAdapter; 324 private List<String> mSettingsMainTextsList; 325 private List<String> mSettingsSubTextsList; 326 private List<Integer> mSettingsIconIdsList; 327 private List<String> mSubtitleDescriptionsList; 328 private List<String> mAudioTrackList; 329 private List<String> mVideoQualityList; 330 private List<String> mPlaybackSpeedTextList; 331 private List<Float> mPlaybackSpeedList; 332 MediaControlView2(@onNull Context context)333 public MediaControlView2(@NonNull Context context) { 334 this(context, null); 335 } 336 MediaControlView2(@onNull Context context, @Nullable AttributeSet attrs)337 public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) { 338 this(context, attrs, 0); 339 } 340 MediaControlView2(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)341 public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, 342 int defStyleAttr) { 343 super(context, attrs, defStyleAttr); 344 345 mResources = context.getResources(); 346 // Inflate MediaControlView2 from XML 347 mRoot = makeControllerView(); 348 addView(mRoot); 349 } 350 351 /** 352 * Sets MediaSession2 token to control corresponding MediaSession2. 353 * @hide 354 */ 355 @RestrictTo(LIBRARY_GROUP) setMediaSessionToken(SessionToken2 token)356 public void setMediaSessionToken(SessionToken2 token) { 357 } 358 359 /** 360 * Registers a callback to be invoked when the fullscreen mode should be changed. 361 * @param l The callback that will be run 362 */ setOnFullScreenListener(OnFullScreenListener l)363 public void setOnFullScreenListener(OnFullScreenListener l) { 364 mOnFullScreenListener = l; 365 } 366 367 /** 368 * Sets MediaController instance to MediaControlView2, which makes it possible to send and 369 * receive data between MediaControlView2 and VideoView2. This method does not need to be called 370 * when MediaControlView2 is initialized with VideoView2. 371 * @hide TODO: remove once the implementation is revised 372 */ 373 @RestrictTo(LIBRARY_GROUP) setController(MediaControllerCompat controller)374 public void setController(MediaControllerCompat controller) { 375 mController = controller; 376 if (controller != null) { 377 mControls = mController.getTransportControls(); 378 // Set mMetadata and mPlaybackState to existing MediaSession variables since they may 379 // be called before the callback is called 380 mPlaybackState = mController.getPlaybackState(); 381 mMetadata = mController.getMetadata(); 382 updateDuration(); 383 updateTitle(); 384 385 mController.registerCallback(new MediaControllerCallback()); 386 } 387 } 388 389 /** 390 * Changes the visibility state of an individual button. Default value is View.Visible. 391 * 392 * @param button the {@code Button} assigned to individual buttons 393 * <ul> 394 * <li>{@link #BUTTON_PLAY_PAUSE} 395 * <li>{@link #BUTTON_FFWD} 396 * <li>{@link #BUTTON_REW} 397 * <li>{@link #BUTTON_NEXT} 398 * <li>{@link #BUTTON_PREV} 399 * <li>{@link #BUTTON_SUBTITLE} 400 * <li>{@link #BUTTON_FULL_SCREEN} 401 * <li>{@link #BUTTON_MUTE} 402 * <li>{@link #BUTTON_OVERFLOW} 403 * <li>{@link #BUTTON_ASPECT_RATIO} 404 * <li>{@link #BUTTON_SETTINGS} 405 * </ul> 406 * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. 407 * @hide 408 */ 409 @RestrictTo(LIBRARY_GROUP) setButtonVisibility(@utton int button, int visibility)410 public void setButtonVisibility(@Button int button, /*@Visibility*/ int visibility) { 411 switch (button) { 412 case MediaControlView2.BUTTON_PLAY_PAUSE: 413 if (mPlayPauseButton != null && canPause()) { 414 mPlayPauseButton.setVisibility(visibility); 415 } 416 break; 417 case MediaControlView2.BUTTON_FFWD: 418 if (mFfwdButton != null && canSeekForward()) { 419 mFfwdButton.setVisibility(visibility); 420 } 421 break; 422 case MediaControlView2.BUTTON_REW: 423 if (mRewButton != null && canSeekBackward()) { 424 mRewButton.setVisibility(visibility); 425 } 426 break; 427 case MediaControlView2.BUTTON_NEXT: 428 if (mNextButton != null) { 429 mNextButton.setVisibility(visibility); 430 } 431 break; 432 case MediaControlView2.BUTTON_PREV: 433 if (mPrevButton != null) { 434 mPrevButton.setVisibility(visibility); 435 } 436 break; 437 case MediaControlView2.BUTTON_SUBTITLE: 438 if (mSubtitleButton != null && mSubtitleTrackCount > 0) { 439 mSubtitleButton.setVisibility(visibility); 440 } 441 break; 442 case MediaControlView2.BUTTON_FULL_SCREEN: 443 if (mFullScreenButton != null) { 444 mFullScreenButton.setVisibility(visibility); 445 } 446 break; 447 case MediaControlView2.BUTTON_OVERFLOW: 448 if (mOverflowButtonRight != null) { 449 mOverflowButtonRight.setVisibility(visibility); 450 } 451 break; 452 case MediaControlView2.BUTTON_MUTE: 453 if (mMuteButton != null) { 454 mMuteButton.setVisibility(visibility); 455 } 456 break; 457 case MediaControlView2.BUTTON_SETTINGS: 458 if (mSettingsButton != null) { 459 mSettingsButton.setVisibility(visibility); 460 } 461 break; 462 default: 463 break; 464 } 465 } 466 467 /** 468 * Requests focus for the play/pause button. 469 */ requestPlayButtonFocus()470 public void requestPlayButtonFocus() { 471 if (mPlayPauseButton != null) { 472 mPlayPauseButton.requestFocus(); 473 } 474 } 475 476 /** 477 * Interface definition of a callback to be invoked to inform the fullscreen mode is changed. 478 * Application should handle the fullscreen mode accordingly. 479 */ 480 public interface OnFullScreenListener { 481 /** 482 * Called to indicate a fullscreen mode change. 483 */ onFullScreen(View view, boolean fullScreen)484 void onFullScreen(View view, boolean fullScreen); 485 } 486 487 @Override getAccessibilityClassName()488 public CharSequence getAccessibilityClassName() { 489 return MediaControlView2.class.getName(); 490 } 491 492 @Override onTouchEvent(MotionEvent ev)493 public boolean onTouchEvent(MotionEvent ev) { 494 return false; 495 } 496 497 @Override onTrackballEvent(MotionEvent ev)498 public boolean onTrackballEvent(MotionEvent ev) { 499 return false; 500 } 501 502 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)503 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 504 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 505 // Update layout when this view's width changes in order to avoid any UI overlap between 506 // transport controls. 507 if (mPrevWidth != getMeasuredWidth() 508 || mPrevHeight != getMeasuredHeight() || mNeedUXUpdate) { 509 // Dismiss SettingsWindow if it is showing. 510 mSettingsWindow.dismiss(); 511 512 // These views may not have been initialized yet. 513 if (mTransportControls.getWidth() == 0 || mTimeView.getWidth() == 0) { 514 return; 515 } 516 517 int currWidth = getMeasuredWidth(); 518 int currHeight = getMeasuredHeight(); 519 WindowManager manager = (WindowManager) getContext().getApplicationContext() 520 .getSystemService(Context.WINDOW_SERVICE); 521 Point screenSize = new Point(); 522 manager.getDefaultDisplay().getSize(screenSize); 523 int screenWidth = screenSize.x; 524 int screenHeight = screenSize.y; 525 int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_size); 526 527 if (mMediaType == MEDIA_TYPE_DEFAULT) { 528 // Max number of icons inside BottomBarRightView for Music mode is 4. 529 int maxIconCount = 4; 530 updateLayout(maxIconCount, iconSize, currWidth, currHeight, screenWidth, 531 screenHeight); 532 533 } else if (mMediaType == MEDIA_TYPE_MUSIC) { 534 if (mNeedUXUpdate) { 535 // One-time operation for Music media type 536 mBasicControls.removeView(mMuteButton); 537 mExtraControls.addView(mMuteButton, 0); 538 mVideoQualityButton.setVisibility(View.GONE); 539 if (mFfwdButton != null) { 540 mFfwdButton.setVisibility(View.GONE); 541 } 542 if (mRewButton != null) { 543 mRewButton.setVisibility(View.GONE); 544 } 545 } 546 mNeedUXUpdate = false; 547 548 // Max number of icons inside BottomBarRightView for Music mode is 3. 549 int maxIconCount = 3; 550 updateLayout(maxIconCount, iconSize, currWidth, currHeight, screenWidth, 551 screenHeight); 552 } 553 mPrevWidth = currWidth; 554 mPrevHeight = currHeight; 555 } 556 // Update title bar parameters in order to avoid overlap between title view and the right 557 // side of the title bar. 558 updateTitleBarLayout(); 559 } 560 561 @Override setEnabled(boolean enabled)562 public void setEnabled(boolean enabled) { 563 super.setEnabled(enabled); 564 565 if (mPlayPauseButton != null) { 566 mPlayPauseButton.setEnabled(enabled); 567 } 568 if (mFfwdButton != null) { 569 mFfwdButton.setEnabled(enabled); 570 } 571 if (mRewButton != null) { 572 mRewButton.setEnabled(enabled); 573 } 574 if (mNextButton != null) { 575 mNextButton.setEnabled(enabled); 576 } 577 if (mPrevButton != null) { 578 mPrevButton.setEnabled(enabled); 579 } 580 if (mProgress != null) { 581 mProgress.setEnabled(enabled); 582 } 583 disableUnsupportedButtons(); 584 } 585 586 @Override onVisibilityAggregated(boolean isVisible)587 public void onVisibilityAggregated(boolean isVisible) { 588 super.onVisibilityAggregated(isVisible); 589 590 if (isVisible) { 591 disableUnsupportedButtons(); 592 removeCallbacks(mUpdateProgress); 593 post(mUpdateProgress); 594 } else { 595 removeCallbacks(mUpdateProgress); 596 } 597 } 598 setRouteSelector(MediaRouteSelector selector)599 void setRouteSelector(MediaRouteSelector selector) { 600 mRouteSelector = selector; 601 if (mRouteSelector != null && !mRouteSelector.isEmpty()) { 602 mRouteButton.setRouteSelector(selector); 603 mRouteButton.setVisibility(View.VISIBLE); 604 } else { 605 mRouteButton.setRouteSelector(MediaRouteSelector.EMPTY); 606 mRouteButton.setVisibility(View.GONE); 607 } 608 } 609 610 /////////////////////////////////////////////////// 611 // Protected or private methods 612 /////////////////////////////////////////////////// 613 isPlaying()614 private boolean isPlaying() { 615 if (mPlaybackState != null) { 616 return mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING; 617 } 618 return false; 619 } 620 getCurrentPosition()621 private int getCurrentPosition() { 622 mPlaybackState = mController.getPlaybackState(); 623 if (mPlaybackState != null) { 624 return (int) mPlaybackState.getPosition(); 625 } 626 return 0; 627 } 628 getBufferPercentage()629 private int getBufferPercentage() { 630 if (mDuration == 0) { 631 return 0; 632 } 633 mPlaybackState = mController.getPlaybackState(); 634 if (mPlaybackState != null) { 635 long bufferedPos = mPlaybackState.getBufferedPosition(); 636 return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration); 637 } 638 return 0; 639 } 640 canPause()641 private boolean canPause() { 642 if (mPlaybackState != null) { 643 return (mPlaybackState.getActions() & PlaybackStateCompat.ACTION_PAUSE) != 0; 644 } 645 return true; 646 } 647 canSeekBackward()648 private boolean canSeekBackward() { 649 if (mPlaybackState != null) { 650 return (mPlaybackState.getActions() & PlaybackStateCompat.ACTION_REWIND) != 0; 651 } 652 return true; 653 } 654 canSeekForward()655 private boolean canSeekForward() { 656 if (mPlaybackState != null) { 657 return (mPlaybackState.getActions() & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0; 658 } 659 return true; 660 } 661 662 /** 663 * Create the view that holds the widgets that control playback. 664 * Derived classes can override this to create their own. 665 * 666 * @return The controller view. 667 */ makeControllerView()668 private ViewGroup makeControllerView() { 669 ViewGroup root = (ViewGroup) inflateLayout(getContext(), R.layout.media_controller); 670 initControllerView(root); 671 return root; 672 } 673 inflateLayout(Context context, int resId)674 private View inflateLayout(Context context, int resId) { 675 LayoutInflater inflater = (LayoutInflater) context 676 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 677 return inflater.inflate(resId, null); 678 } 679 680 @SuppressWarnings("deprecation") initControllerView(ViewGroup v)681 private void initControllerView(ViewGroup v) { 682 // Relating to Title Bar View 683 mTitleBar = v.findViewById(R.id.title_bar); 684 mTitleView = v.findViewById(R.id.title_text); 685 mAdExternalLink = v.findViewById(R.id.ad_external_link); 686 mBackButton = v.findViewById(R.id.back); 687 if (mBackButton != null) { 688 mBackButton.setOnClickListener(mBackListener); 689 mBackButton.setVisibility(View.GONE); 690 } 691 mRouteButton = v.findViewById(R.id.cast); 692 693 // Relating to Center View 694 mCenterView = v.findViewById(R.id.center_view); 695 mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls); 696 mCenterView.addView(mTransportControls); 697 698 // Relating to Minimal Extra View 699 mMinimalExtraView = (LinearLayout) v.findViewById(R.id.minimal_extra_view); 700 LinearLayout.LayoutParams params = 701 (LinearLayout.LayoutParams) mMinimalExtraView.getLayoutParams(); 702 int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_size); 703 int marginSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_margin); 704 params.setMargins(0, (iconSize + marginSize * 2) * (-1), 0, 0); 705 mMinimalExtraView.setLayoutParams(params); 706 mMinimalExtraView.setVisibility(View.GONE); 707 708 // Relating to Progress Bar View 709 mProgress = v.findViewById(R.id.progress); 710 if (mProgress != null) { 711 if (mProgress instanceof SeekBar) { 712 SeekBar seeker = (SeekBar) mProgress; 713 seeker.setOnSeekBarChangeListener(mSeekListener); 714 seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress)); 715 seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb)); 716 } 717 mProgress.setMax(MAX_PROGRESS); 718 } 719 mProgressBuffer = v.findViewById(R.id.progress_buffer); 720 721 // Relating to Bottom Bar View 722 mBottomBar = v.findViewById(R.id.bottom_bar); 723 724 // Relating to Bottom Bar Left View 725 mBottomBarLeftView = v.findViewById(R.id.bottom_bar_left); 726 mTimeView = v.findViewById(R.id.time); 727 mEndTime = v.findViewById(R.id.time_end); 728 mCurrentTime = v.findViewById(R.id.time_current); 729 mAdSkipView = v.findViewById(R.id.ad_skip_time); 730 mFormatBuilder = new StringBuilder(); 731 mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); 732 733 // Relating to Bottom Bar Right View 734 mBottomBarRightView = v.findViewById(R.id.bottom_bar_right); 735 mBasicControls = v.findViewById(R.id.basic_controls); 736 mExtraControls = v.findViewById(R.id.extra_controls); 737 mCustomButtons = v.findViewById(R.id.custom_buttons); 738 mSubtitleButton = v.findViewById(R.id.subtitle); 739 if (mSubtitleButton != null) { 740 mSubtitleButton.setOnClickListener(mSubtitleListener); 741 } 742 mFullScreenButton = v.findViewById(R.id.fullscreen); 743 if (mFullScreenButton != null) { 744 mFullScreenButton.setOnClickListener(mFullScreenListener); 745 } 746 mOverflowButtonRight = v.findViewById(R.id.overflow_right); 747 if (mOverflowButtonRight != null) { 748 mOverflowButtonRight.setOnClickListener(mOverflowRightListener); 749 } 750 mOverflowButtonLeft = v.findViewById(R.id.overflow_left); 751 if (mOverflowButtonLeft != null) { 752 mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener); 753 } 754 mMuteButton = v.findViewById(R.id.mute); 755 if (mMuteButton != null) { 756 mMuteButton.setOnClickListener(mMuteButtonListener); 757 } 758 mSettingsButton = v.findViewById(R.id.settings); 759 if (mSettingsButton != null) { 760 mSettingsButton.setOnClickListener(mSettingsButtonListener); 761 } 762 mVideoQualityButton = v.findViewById(R.id.video_quality); 763 if (mVideoQualityButton != null) { 764 mVideoQualityButton.setOnClickListener(mVideoQualityListener); 765 } 766 mAdRemainingView = v.findViewById(R.id.ad_remaining); 767 768 // Relating to Settings List View 769 initializeSettingsLists(); 770 mSettingsListView = (ListView) inflateLayout(getContext(), R.layout.settings_list); 771 mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList, 772 mSettingsIconIdsList); 773 mSubSettingsAdapter = new SubSettingsAdapter(null, 0); 774 mSettingsListView.setAdapter(mSettingsAdapter); 775 mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 776 mSettingsListView.setOnItemClickListener(mSettingsItemClickListener); 777 778 mEmbeddedSettingsItemWidth = mResources.getDimensionPixelSize( 779 R.dimen.mcv2_embedded_settings_width); 780 mFullSettingsItemWidth = mResources.getDimensionPixelSize(R.dimen.mcv2_full_settings_width); 781 mSettingsItemHeight = mResources.getDimensionPixelSize( 782 R.dimen.mcv2_settings_height); 783 mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize( 784 R.dimen.mcv2_settings_offset); 785 mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth, 786 LayoutParams.WRAP_CONTENT, true); 787 } 788 789 /** 790 * Disable pause or seek buttons if the stream cannot be paused or seeked. 791 * This requires the control interface to be a MediaPlayerControlExt 792 */ disableUnsupportedButtons()793 private void disableUnsupportedButtons() { 794 try { 795 if (mPlayPauseButton != null && !canPause()) { 796 mPlayPauseButton.setEnabled(false); 797 } 798 if (mRewButton != null && !canSeekBackward()) { 799 mRewButton.setEnabled(false); 800 } 801 if (mFfwdButton != null && !canSeekForward()) { 802 mFfwdButton.setEnabled(false); 803 } 804 if (mProgress != null && !canSeekBackward() && !canSeekForward()) { 805 mProgress.setEnabled(false); 806 } 807 } catch (IncompatibleClassChangeError ex) { 808 // We were given an old version of the interface, that doesn't have 809 // the canPause/canSeekXYZ methods. This is OK, it just means we 810 // assume the media can be paused and seeked, and so we don't disable 811 // the buttons. 812 } 813 } 814 815 private final Runnable mUpdateProgress = new Runnable() { 816 @Override 817 public void run() { 818 int pos = setProgress(); 819 boolean isShowing = getVisibility() == View.VISIBLE; 820 if (!mDragging && isShowing && isPlaying()) { 821 postDelayed(mUpdateProgress, 822 DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS)); 823 } 824 } 825 }; 826 stringForTime(int timeMs)827 private String stringForTime(int timeMs) { 828 int totalSeconds = timeMs / 1000; 829 830 int seconds = totalSeconds % 60; 831 int minutes = (totalSeconds / 60) % 60; 832 int hours = totalSeconds / 3600; 833 834 mFormatBuilder.setLength(0); 835 if (hours > 0) { 836 return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); 837 } else { 838 return mFormatter.format("%02d:%02d", minutes, seconds).toString(); 839 } 840 } 841 setProgress()842 private int setProgress() { 843 if (mController == null || mDragging) { 844 return 0; 845 } 846 int positionOnProgressBar = 0; 847 int currentPosition = getCurrentPosition(); 848 if (mDuration > 0) { 849 positionOnProgressBar = (int) (MAX_PROGRESS * (long) currentPosition / mDuration); 850 } 851 if (mProgress != null && currentPosition != mDuration) { 852 mProgress.setProgress(positionOnProgressBar); 853 // If the media is a local file, there is no need to set a buffer, so set secondary 854 // progress to maximum. 855 if (getBufferPercentage() < 0) { 856 mProgress.setSecondaryProgress(MAX_PROGRESS); 857 } else { 858 mProgress.setSecondaryProgress(getBufferPercentage() * 10); 859 } 860 } 861 862 if (mEndTime != null) { 863 mEndTime.setText(stringForTime(mDuration)); 864 865 } 866 if (mCurrentTime != null) { 867 mCurrentTime.setText(stringForTime(currentPosition)); 868 } 869 870 if (mIsAdvertisement) { 871 // Update the remaining number of seconds until the first 5 seconds of the 872 // advertisement. 873 if (mAdSkipView != null) { 874 if (currentPosition <= AD_SKIP_WAIT_TIME_MS) { 875 if (mAdSkipView.getVisibility() == View.GONE) { 876 mAdSkipView.setVisibility(View.VISIBLE); 877 } 878 String skipTimeText = mResources.getString( 879 R.string.MediaControlView2_ad_skip_wait_time, 880 ((AD_SKIP_WAIT_TIME_MS - currentPosition) / 1000 + 1)); 881 mAdSkipView.setText(skipTimeText); 882 } else { 883 if (mAdSkipView.getVisibility() == View.VISIBLE) { 884 mAdSkipView.setVisibility(View.GONE); 885 mNextButton.setEnabled(true); 886 mNextButton.clearColorFilter(); 887 } 888 } 889 } 890 // Update the remaining number of seconds of the advertisement. 891 if (mAdRemainingView != null) { 892 int remainingTime = 893 (mDuration - currentPosition < 0) ? 0 : (mDuration - currentPosition); 894 String remainingTimeText = mResources.getString( 895 R.string.MediaControlView2_ad_remaining_time, 896 stringForTime(remainingTime)); 897 mAdRemainingView.setText(remainingTimeText); 898 } 899 } 900 return currentPosition; 901 } 902 togglePausePlayState()903 private void togglePausePlayState() { 904 if (isPlaying()) { 905 mControls.pause(); 906 mPlayPauseButton.setImageDrawable( 907 mResources.getDrawable(R.drawable.ic_play_circle_filled, null)); 908 mPlayPauseButton.setContentDescription( 909 mResources.getString(R.string.mcv2_play_button_desc)); 910 } else { 911 mControls.play(); 912 mPlayPauseButton.setImageDrawable( 913 mResources.getDrawable(R.drawable.ic_pause_circle_filled, null)); 914 mPlayPauseButton.setContentDescription( 915 mResources.getString(R.string.mcv2_pause_button_desc)); 916 } 917 } 918 919 // There are two scenarios that can trigger the seekbar listener to trigger: 920 // 921 // The first is the user using the touchpad to adjust the posititon of the 922 // seekbar's thumb. In this case onStartTrackingTouch is called followed by 923 // a number of onProgressChanged notifications, concluded by onStopTrackingTouch. 924 // We're setting the field "mDragging" to true for the duration of the dragging 925 // session to avoid jumps in the position in case of ongoing playback. 926 // 927 // The second scenario involves the user operating the scroll ball, in this 928 // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications, 929 // we will simply apply the updated position without suspending regular updates. 930 private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { 931 @Override 932 public void onStartTrackingTouch(SeekBar bar) { 933 if (!mSeekAvailable) { 934 return; 935 } 936 937 mDragging = true; 938 939 // By removing these pending progress messages we make sure 940 // that a) we won't update the progress while the user adjusts 941 // the seekbar and b) once the user is done dragging the thumb 942 // we will post one of these messages to the queue again and 943 // this ensures that there will be exactly one message queued up. 944 removeCallbacks(mUpdateProgress); 945 946 // Check if playback is currently stopped. In this case, update the pause button to 947 // show the play image instead of the replay image. 948 if (mIsStopped) { 949 mPlayPauseButton.setImageDrawable( 950 mResources.getDrawable(R.drawable.ic_play_circle_filled, null)); 951 mPlayPauseButton.setContentDescription( 952 mResources.getString(R.string.mcv2_play_button_desc)); 953 mIsStopped = false; 954 } 955 } 956 957 @Override 958 public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) { 959 if (!mSeekAvailable) { 960 return; 961 } 962 if (!fromUser) { 963 // We're not interested in programmatically generated changes to 964 // the progress bar's position. 965 return; 966 } 967 if (mDuration > 0) { 968 int position = (int) (((long) mDuration * progress) / MAX_PROGRESS); 969 mControls.seekTo(position); 970 971 if (mCurrentTime != null) { 972 mCurrentTime.setText(stringForTime(position)); 973 } 974 } 975 } 976 977 @Override 978 public void onStopTrackingTouch(SeekBar bar) { 979 if (!mSeekAvailable) { 980 return; 981 } 982 mDragging = false; 983 984 setProgress(); 985 986 // Ensure that progress is properly updated in the future, 987 // the call to show() does not guarantee this because it is a 988 // no-op if we are already showing. 989 post(mUpdateProgress); 990 } 991 }; 992 993 private final OnClickListener mPlayPauseListener = new OnClickListener() { 994 @Override 995 public void onClick(View v) { 996 togglePausePlayState(); 997 } 998 }; 999 1000 private final OnClickListener mRewListener = new OnClickListener() { 1001 @Override 1002 public void onClick(View v) { 1003 int pos = getCurrentPosition() - REWIND_TIME_MS; 1004 mControls.seekTo(pos); 1005 setProgress(); 1006 } 1007 }; 1008 1009 private final OnClickListener mFfwdListener = new OnClickListener() { 1010 @Override 1011 public void onClick(View v) { 1012 int pos = getCurrentPosition() + FORWARD_TIME_MS; 1013 mControls.seekTo(pos); 1014 setProgress(); 1015 } 1016 }; 1017 1018 private final OnClickListener mNextListener = new OnClickListener() { 1019 @Override 1020 public void onClick(View v) { 1021 mControls.skipToNext(); 1022 } 1023 }; 1024 1025 private final OnClickListener mPrevListener = new OnClickListener() { 1026 @Override 1027 public void onClick(View v) { 1028 mControls.skipToPrevious(); 1029 } 1030 }; 1031 1032 private final OnClickListener mBackListener = new OnClickListener() { 1033 @Override 1034 public void onClick(View v) { 1035 View parent = (View) getParent(); 1036 if (parent != null) { 1037 parent.onKeyDown(KeyEvent.KEYCODE_BACK, 1038 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)); 1039 } 1040 } 1041 }; 1042 1043 private final OnClickListener mSubtitleListener = new OnClickListener() { 1044 @Override 1045 public void onClick(View v) { 1046 mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK; 1047 mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList); 1048 mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex); 1049 displaySettingsWindow(mSubSettingsAdapter); 1050 } 1051 }; 1052 1053 private final OnClickListener mVideoQualityListener = new OnClickListener() { 1054 @Override 1055 public void onClick(View v) { 1056 mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY; 1057 mSubSettingsAdapter.setTexts(mVideoQualityList); 1058 mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex); 1059 displaySettingsWindow(mSubSettingsAdapter); 1060 } 1061 }; 1062 1063 private final OnClickListener mFullScreenListener = new OnClickListener() { 1064 @Override 1065 public void onClick(View v) { 1066 if (mOnFullScreenListener == null) { 1067 return; 1068 } 1069 1070 final boolean isEnteringFullScreen = !mIsFullScreen; 1071 if (isEnteringFullScreen) { 1072 mFullScreenButton.setImageDrawable( 1073 mResources.getDrawable(R.drawable.ic_fullscreen_exit, null)); 1074 } else { 1075 mFullScreenButton.setImageDrawable( 1076 mResources.getDrawable(R.drawable.ic_fullscreen, null)); 1077 } 1078 mIsFullScreen = isEnteringFullScreen; 1079 mOnFullScreenListener.onFullScreen(MediaControlView2.this, 1080 mIsFullScreen); 1081 } 1082 }; 1083 1084 private final OnClickListener mOverflowRightListener = new OnClickListener() { 1085 @Override 1086 public void onClick(View v) { 1087 mBasicControls.setVisibility(View.GONE); 1088 mExtraControls.setVisibility(View.VISIBLE); 1089 } 1090 }; 1091 1092 private final OnClickListener mOverflowLeftListener = new OnClickListener() { 1093 @Override 1094 public void onClick(View v) { 1095 mBasicControls.setVisibility(View.VISIBLE); 1096 mExtraControls.setVisibility(View.GONE); 1097 } 1098 }; 1099 1100 private final OnClickListener mMuteButtonListener = new OnClickListener() { 1101 @Override 1102 public void onClick(View v) { 1103 if (!mIsMute) { 1104 mMuteButton.setImageDrawable( 1105 mResources.getDrawable(R.drawable.ic_mute, null)); 1106 mMuteButton.setContentDescription( 1107 mResources.getString(R.string.mcv2_muted_button_desc)); 1108 mIsMute = true; 1109 mController.sendCommand(COMMAND_MUTE, null, null); 1110 } else { 1111 mMuteButton.setImageDrawable( 1112 mResources.getDrawable(R.drawable.ic_unmute, null)); 1113 mMuteButton.setContentDescription( 1114 mResources.getString(R.string.mcv2_unmuted_button_desc)); 1115 mIsMute = false; 1116 mController.sendCommand(COMMAND_UNMUTE, null, null); 1117 } 1118 } 1119 }; 1120 1121 private final OnClickListener mSettingsButtonListener = new OnClickListener() { 1122 @Override 1123 public void onClick(View v) { 1124 mSettingsMode = SETTINGS_MODE_MAIN; 1125 mSettingsAdapter.setSubTexts(mSettingsSubTextsList); 1126 displaySettingsWindow(mSettingsAdapter); 1127 } 1128 }; 1129 1130 private final AdapterView.OnItemClickListener mSettingsItemClickListener = 1131 new AdapterView.OnItemClickListener() { 1132 @Override 1133 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1134 switch (mSettingsMode) { 1135 case SETTINGS_MODE_MAIN: 1136 if (position == SETTINGS_MODE_AUDIO_TRACK) { 1137 mSubSettingsAdapter.setTexts(mAudioTrackList); 1138 mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex); 1139 mSettingsMode = SETTINGS_MODE_AUDIO_TRACK; 1140 } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) { 1141 mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList); 1142 mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex); 1143 mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED; 1144 } else if (position == SETTINGS_MODE_HELP) { 1145 mSettingsWindow.dismiss(); 1146 return; 1147 } 1148 displaySettingsWindow(mSubSettingsAdapter); 1149 break; 1150 case SETTINGS_MODE_AUDIO_TRACK: 1151 if (position != mSelectedAudioTrackIndex) { 1152 mSelectedAudioTrackIndex = position; 1153 if (mAudioTrackCount > 0) { 1154 Bundle extra = new Bundle(); 1155 extra.putInt(KEY_SELECTED_AUDIO_INDEX, position); 1156 mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null); 1157 } 1158 mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK, 1159 mSubSettingsAdapter.getMainText(position)); 1160 } 1161 mSettingsWindow.dismiss(); 1162 break; 1163 case SETTINGS_MODE_PLAYBACK_SPEED: 1164 if (position != mSelectedSpeedIndex) { 1165 mSelectedSpeedIndex = position; 1166 Bundle extra = new Bundle(); 1167 extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position)); 1168 mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null); 1169 mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED, 1170 mSubSettingsAdapter.getMainText(position)); 1171 } 1172 mSettingsWindow.dismiss(); 1173 break; 1174 case SETTINGS_MODE_HELP: 1175 break; 1176 case SETTINGS_MODE_SUBTITLE_TRACK: 1177 if (position != mSelectedSubtitleTrackIndex) { 1178 mSelectedSubtitleTrackIndex = position; 1179 if (position > 0) { 1180 Bundle extra = new Bundle(); 1181 extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1); 1182 mController.sendCommand(COMMAND_SHOW_SUBTITLE, extra, null); 1183 mSubtitleButton.setImageDrawable( 1184 mResources.getDrawable(R.drawable.ic_subtitle_on, null)); 1185 mSubtitleButton.setContentDescription( 1186 mResources.getString(R.string.mcv2_cc_is_on)); 1187 mSubtitleIsEnabled = true; 1188 } else { 1189 mController.sendCommand(COMMAND_HIDE_SUBTITLE, null, null); 1190 mSubtitleButton.setImageDrawable( 1191 mResources.getDrawable(R.drawable.ic_subtitle_off, null)); 1192 mSubtitleButton.setContentDescription( 1193 mResources.getString(R.string.mcv2_cc_is_off)); 1194 mSubtitleIsEnabled = false; 1195 } 1196 } 1197 mSettingsWindow.dismiss(); 1198 break; 1199 case SETTINGS_MODE_VIDEO_QUALITY: 1200 mSelectedVideoQualityIndex = position; 1201 mSettingsWindow.dismiss(); 1202 break; 1203 } 1204 } 1205 }; 1206 updateDuration()1207 private void updateDuration() { 1208 if (mMetadata != null) { 1209 if (mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) { 1210 mDuration = (int) mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION); 1211 // update progress bar 1212 setProgress(); 1213 } 1214 } 1215 } 1216 updateTitle()1217 private void updateTitle() { 1218 if (mMetadata != null) { 1219 if (mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_TITLE)) { 1220 mTitleView.setText(mMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)); 1221 } 1222 } 1223 } 1224 1225 // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are 1226 // greater than the length of the title bar, reduce the size of the left bar (which makes the 1227 // TextView that contains the title of the media file shrink). updateTitleBarLayout()1228 private void updateTitleBarLayout() { 1229 if (mTitleBar != null) { 1230 int titleBarWidth = mTitleBar.getWidth(); 1231 1232 View leftBar = mTitleBar.findViewById(R.id.title_bar_left); 1233 View rightBar = mTitleBar.findViewById(R.id.title_bar_right); 1234 int leftBarWidth = leftBar.getWidth(); 1235 int rightBarWidth = rightBar.getWidth(); 1236 1237 RelativeLayout.LayoutParams params = 1238 (RelativeLayout.LayoutParams) leftBar.getLayoutParams(); 1239 if (leftBarWidth + rightBarWidth > titleBarWidth) { 1240 params.width = titleBarWidth - rightBarWidth; 1241 mOriginalLeftBarWidth = leftBarWidth; 1242 } else if (leftBarWidth + rightBarWidth < titleBarWidth && mOriginalLeftBarWidth != 0) { 1243 params.width = mOriginalLeftBarWidth; 1244 mOriginalLeftBarWidth = 0; 1245 } 1246 leftBar.setLayoutParams(params); 1247 } 1248 } 1249 updateAudioMetadata()1250 private void updateAudioMetadata() { 1251 if (mMediaType != MEDIA_TYPE_MUSIC) { 1252 return; 1253 } 1254 1255 if (mMetadata != null) { 1256 String titleText = ""; 1257 String artistText = ""; 1258 if (mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_TITLE)) { 1259 titleText = mMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE); 1260 } else { 1261 titleText = mResources.getString(R.string.mcv2_music_title_unknown_text); 1262 } 1263 1264 if (mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_ARTIST)) { 1265 artistText = mMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST); 1266 } else { 1267 artistText = mResources.getString(R.string.mcv2_music_artist_unknown_text); 1268 } 1269 1270 // Update title for Embedded size type 1271 mTitleView.setText(titleText + " - " + artistText); 1272 1273 // Set to true to update layout inside onMeasure() 1274 mNeedUXUpdate = true; 1275 } 1276 } 1277 updateLayout()1278 private void updateLayout() { 1279 if (mIsAdvertisement) { 1280 mRewButton.setVisibility(View.GONE); 1281 mFfwdButton.setVisibility(View.GONE); 1282 mPrevButton.setVisibility(View.GONE); 1283 mTimeView.setVisibility(View.GONE); 1284 1285 mAdSkipView.setVisibility(View.VISIBLE); 1286 mAdRemainingView.setVisibility(View.VISIBLE); 1287 mAdExternalLink.setVisibility(View.VISIBLE); 1288 1289 mProgress.setEnabled(false); 1290 mNextButton.setEnabled(false); 1291 mNextButton.setColorFilter(R.color.gray); 1292 } else { 1293 mRewButton.setVisibility(View.VISIBLE); 1294 mFfwdButton.setVisibility(View.VISIBLE); 1295 mPrevButton.setVisibility(View.VISIBLE); 1296 mTimeView.setVisibility(View.VISIBLE); 1297 1298 mAdSkipView.setVisibility(View.GONE); 1299 mAdRemainingView.setVisibility(View.GONE); 1300 mAdExternalLink.setVisibility(View.GONE); 1301 1302 mProgress.setEnabled(true); 1303 mNextButton.setEnabled(true); 1304 mNextButton.clearColorFilter(); 1305 disableUnsupportedButtons(); 1306 } 1307 } 1308 updateLayout(int maxIconCount, int iconSize, int currWidth, int currHeight, int screenWidth, int screenHeight)1309 private void updateLayout(int maxIconCount, int iconSize, int currWidth, 1310 int currHeight, int screenWidth, int screenHeight) { 1311 int bottomBarRightWidthMax = iconSize * maxIconCount; 1312 int fullWidth = mTransportControls.getWidth() + mTimeView.getWidth() 1313 + bottomBarRightWidthMax; 1314 int embeddedWidth = mTimeView.getWidth() + bottomBarRightWidthMax; 1315 int screenMaxLength = Math.max(screenWidth, screenHeight); 1316 1317 boolean isFullSize = (mMediaType == MEDIA_TYPE_DEFAULT) ? (currWidth == screenMaxLength) : 1318 (currWidth == screenWidth && currHeight == screenHeight); 1319 1320 if (isFullSize) { 1321 if (mSizeType != SIZE_TYPE_FULL) { 1322 updateLayoutForSizeChange(SIZE_TYPE_FULL); 1323 if (mMediaType == MEDIA_TYPE_MUSIC) { 1324 mTitleView.setVisibility(View.GONE); 1325 } 1326 } 1327 } else if (embeddedWidth <= currWidth) { 1328 if (mSizeType != SIZE_TYPE_EMBEDDED) { 1329 updateLayoutForSizeChange(SIZE_TYPE_EMBEDDED); 1330 if (mMediaType == MEDIA_TYPE_MUSIC) { 1331 mTitleView.setVisibility(View.VISIBLE); 1332 } 1333 } 1334 } else { 1335 if (mSizeType != SIZE_TYPE_MINIMAL) { 1336 updateLayoutForSizeChange(SIZE_TYPE_MINIMAL); 1337 if (mMediaType == MEDIA_TYPE_MUSIC) { 1338 mTitleView.setVisibility(View.GONE); 1339 } 1340 } 1341 } 1342 } 1343 1344 @SuppressWarnings("deprecation") updateLayoutForSizeChange(int sizeType)1345 private void updateLayoutForSizeChange(int sizeType) { 1346 mSizeType = sizeType; 1347 RelativeLayout.LayoutParams timeViewParams = 1348 (RelativeLayout.LayoutParams) mTimeView.getLayoutParams(); 1349 SeekBar seeker = (SeekBar) mProgress; 1350 switch (mSizeType) { 1351 case SIZE_TYPE_EMBEDDED: 1352 // Relating to Title Bar 1353 mTitleBar.setVisibility(View.VISIBLE); 1354 mBackButton.setVisibility(View.GONE); 1355 mTitleView.setPadding( 1356 mResources.getDimensionPixelSize(R.dimen.mcv2_embedded_icon_padding), 1357 mTitleView.getPaddingTop(), 1358 mTitleView.getPaddingRight(), 1359 mTitleView.getPaddingBottom()); 1360 1361 // Relating to Full Screen Button 1362 mMinimalExtraView.setVisibility(View.GONE); 1363 mFullScreenButton = mBottomBarRightView.findViewById(R.id.fullscreen); 1364 mFullScreenButton.setOnClickListener(mFullScreenListener); 1365 1366 // Relating to Center View 1367 mCenterView.removeAllViews(); 1368 mBottomBarLeftView.removeView(mTransportControls); 1369 mBottomBarLeftView.setVisibility(View.GONE); 1370 mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls); 1371 mCenterView.addView(mTransportControls); 1372 1373 // Relating to Progress Bar 1374 seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb)); 1375 mProgressBuffer.setVisibility(View.VISIBLE); 1376 1377 // Relating to Bottom Bar 1378 mBottomBar.setVisibility(View.VISIBLE); 1379 if (timeViewParams.getRules()[RelativeLayout.LEFT_OF] != 0) { 1380 timeViewParams.removeRule(RelativeLayout.LEFT_OF); 1381 timeViewParams.addRule(RelativeLayout.RIGHT_OF, R.id.bottom_bar_left); 1382 } 1383 break; 1384 case SIZE_TYPE_FULL: 1385 // Relating to Title Bar 1386 mTitleBar.setVisibility(View.VISIBLE); 1387 mBackButton.setVisibility(View.VISIBLE); 1388 mTitleView.setPadding( 1389 0, 1390 mTitleView.getPaddingTop(), 1391 mTitleView.getPaddingRight(), 1392 mTitleView.getPaddingBottom()); 1393 1394 // Relating to Full Screen Button 1395 mMinimalExtraView.setVisibility(View.GONE); 1396 mFullScreenButton = mBottomBarRightView.findViewById(R.id.fullscreen); 1397 mFullScreenButton.setOnClickListener(mFullScreenListener); 1398 1399 // Relating to Center View 1400 mCenterView.removeAllViews(); 1401 mBottomBarLeftView.removeView(mTransportControls); 1402 mTransportControls = inflateTransportControls(R.layout.full_transport_controls); 1403 mBottomBarLeftView.addView(mTransportControls, 0); 1404 mBottomBarLeftView.setVisibility(View.VISIBLE); 1405 1406 // Relating to Progress Bar 1407 seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb)); 1408 mProgressBuffer.setVisibility(View.VISIBLE); 1409 1410 // Relating to Bottom Bar 1411 mBottomBar.setVisibility(View.VISIBLE); 1412 if (timeViewParams.getRules()[RelativeLayout.RIGHT_OF] != 0) { 1413 timeViewParams.removeRule(RelativeLayout.RIGHT_OF); 1414 timeViewParams.addRule(RelativeLayout.LEFT_OF, R.id.bottom_bar_right); 1415 } 1416 break; 1417 case SIZE_TYPE_MINIMAL: 1418 // Relating to Title Bar 1419 mTitleBar.setVisibility(View.GONE); 1420 mBackButton.setVisibility(View.GONE); 1421 1422 // Relating to Full Screen Button 1423 mMinimalExtraView.setVisibility(View.VISIBLE); 1424 mFullScreenButton = mMinimalExtraView.findViewById(R.id.minimal_fullscreen); 1425 mFullScreenButton.setOnClickListener(mFullScreenListener); 1426 1427 // Relating to Center View 1428 mCenterView.removeAllViews(); 1429 mBottomBarLeftView.removeView(mTransportControls); 1430 mTransportControls = inflateTransportControls(R.layout.minimal_transport_controls); 1431 mCenterView.addView(mTransportControls); 1432 1433 // Relating to Progress Bar 1434 seeker.setThumb(null); 1435 mProgressBuffer.setVisibility(View.GONE); 1436 1437 // Relating to Bottom Bar 1438 mBottomBar.setVisibility(View.GONE); 1439 break; 1440 } 1441 mTimeView.setLayoutParams(timeViewParams); 1442 1443 if (isPlaying()) { 1444 mPlayPauseButton.setImageDrawable( 1445 mResources.getDrawable(R.drawable.ic_pause_circle_filled, null)); 1446 mPlayPauseButton.setContentDescription( 1447 mResources.getString(R.string.mcv2_pause_button_desc)); 1448 } else { 1449 mPlayPauseButton.setImageDrawable( 1450 mResources.getDrawable(R.drawable.ic_play_circle_filled, null)); 1451 mPlayPauseButton.setContentDescription( 1452 mResources.getString(R.string.mcv2_play_button_desc)); 1453 } 1454 1455 if (mIsFullScreen) { 1456 mFullScreenButton.setImageDrawable( 1457 mResources.getDrawable(R.drawable.ic_fullscreen_exit, null)); 1458 } else { 1459 mFullScreenButton.setImageDrawable( 1460 mResources.getDrawable(R.drawable.ic_fullscreen, null)); 1461 } 1462 } 1463 inflateTransportControls(int layoutId)1464 private View inflateTransportControls(int layoutId) { 1465 View v = inflateLayout(getContext(), layoutId); 1466 mPlayPauseButton = v.findViewById(R.id.pause); 1467 if (mPlayPauseButton != null) { 1468 mPlayPauseButton.requestFocus(); 1469 mPlayPauseButton.setOnClickListener(mPlayPauseListener); 1470 } 1471 mFfwdButton = v.findViewById(R.id.ffwd); 1472 if (mFfwdButton != null) { 1473 mFfwdButton.setOnClickListener(mFfwdListener); 1474 if (mMediaType == MEDIA_TYPE_MUSIC) { 1475 mFfwdButton.setVisibility(View.GONE); 1476 } 1477 } 1478 mRewButton = v.findViewById(R.id.rew); 1479 if (mRewButton != null) { 1480 mRewButton.setOnClickListener(mRewListener); 1481 if (mMediaType == MEDIA_TYPE_MUSIC) { 1482 mRewButton.setVisibility(View.GONE); 1483 } 1484 } 1485 mNextButton = v.findViewById(R.id.next); 1486 if (mNextButton != null) { 1487 mNextButton.setOnClickListener(mNextListener); 1488 mNextButton.setVisibility(View.GONE); 1489 } 1490 mPrevButton = v.findViewById(R.id.prev); 1491 if (mPrevButton != null) { 1492 mPrevButton.setOnClickListener(mPrevListener); 1493 mPrevButton.setVisibility(View.GONE); 1494 } 1495 return v; 1496 } 1497 initializeSettingsLists()1498 private void initializeSettingsLists() { 1499 mSettingsMainTextsList = new ArrayList<String>(); 1500 mSettingsMainTextsList.add( 1501 mResources.getString(R.string.MediaControlView2_audio_track_text)); 1502 mSettingsMainTextsList.add( 1503 mResources.getString(R.string.MediaControlView2_playback_speed_text)); 1504 mSettingsMainTextsList.add( 1505 mResources.getString(R.string.MediaControlView2_help_text)); 1506 1507 mSettingsSubTextsList = new ArrayList<String>(); 1508 mSettingsSubTextsList.add( 1509 mResources.getString(R.string.MediaControlView2_audio_track_none_text)); 1510 mSettingsSubTextsList.add( 1511 mResources.getStringArray( 1512 R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]); 1513 mSettingsSubTextsList.add(RESOURCE_EMPTY); 1514 1515 mSettingsIconIdsList = new ArrayList<Integer>(); 1516 mSettingsIconIdsList.add(R.drawable.ic_audiotrack); 1517 mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled); 1518 mSettingsIconIdsList.add(R.drawable.ic_help); 1519 1520 mAudioTrackList = new ArrayList<String>(); 1521 mAudioTrackList.add( 1522 mResources.getString(R.string.MediaControlView2_audio_track_none_text)); 1523 1524 mVideoQualityList = new ArrayList<String>(); 1525 mVideoQualityList.add( 1526 mResources.getString(R.string.MediaControlView2_video_quality_auto_text)); 1527 1528 mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList( 1529 mResources.getStringArray(R.array.MediaControlView2_playback_speeds))); 1530 // Select the "1x" speed as the default value. 1531 mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX; 1532 1533 mPlaybackSpeedList = new ArrayList<Float>(); 1534 int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100); 1535 for (int i = 0; i < speeds.length; i++) { 1536 float speed = (float) speeds[i] / 100.0f; 1537 mPlaybackSpeedList.add(speed); 1538 } 1539 } 1540 displaySettingsWindow(BaseAdapter adapter)1541 private void displaySettingsWindow(BaseAdapter adapter) { 1542 // Set Adapter 1543 mSettingsListView.setAdapter(adapter); 1544 1545 // Set width of window 1546 int itemWidth = (mSizeType == SIZE_TYPE_EMBEDDED) 1547 ? mEmbeddedSettingsItemWidth : mFullSettingsItemWidth; 1548 mSettingsWindow.setWidth(itemWidth); 1549 1550 // Calculate height of window and show 1551 int totalHeight = adapter.getCount() * mSettingsItemHeight; 1552 mSettingsWindow.dismiss(); 1553 mSettingsWindow.showAsDropDown(this, mSettingsWindowMargin, 1554 mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT); 1555 } 1556 1557 private class MediaControllerCallback extends MediaControllerCompat.Callback { 1558 @Override onPlaybackStateChanged(PlaybackStateCompat state)1559 public void onPlaybackStateChanged(PlaybackStateCompat state) { 1560 mPlaybackState = state; 1561 1562 // Update pause button depending on playback state for the following two reasons: 1563 // 1) Need to handle case where app customizes playback state behavior when app 1564 // activity is resumed. 1565 // 2) Need to handle case where the media file reaches end of duration. 1566 if (mPlaybackState.getState() != mPrevState) { 1567 switch (mPlaybackState.getState()) { 1568 case PlaybackStateCompat.STATE_PLAYING: 1569 mPlayPauseButton.setImageDrawable( 1570 mResources.getDrawable(R.drawable.ic_pause_circle_filled, null)); 1571 mPlayPauseButton.setContentDescription( 1572 mResources.getString(R.string.mcv2_pause_button_desc)); 1573 removeCallbacks(mUpdateProgress); 1574 post(mUpdateProgress); 1575 break; 1576 case PlaybackStateCompat.STATE_PAUSED: 1577 mPlayPauseButton.setImageDrawable( 1578 mResources.getDrawable(R.drawable.ic_play_circle_filled, null)); 1579 mPlayPauseButton.setContentDescription( 1580 mResources.getString(R.string.mcv2_play_button_desc)); 1581 break; 1582 case PlaybackStateCompat.STATE_STOPPED: 1583 mPlayPauseButton.setImageDrawable( 1584 mResources.getDrawable(R.drawable.ic_replay_circle_filled, null)); 1585 mPlayPauseButton.setContentDescription( 1586 mResources.getString(R.string.mcv2_replay_button_desc)); 1587 mIsStopped = true; 1588 break; 1589 default: 1590 break; 1591 } 1592 mPrevState = mPlaybackState.getState(); 1593 } 1594 1595 if (mPlaybackActions != mPlaybackState.getActions()) { 1596 long newActions = mPlaybackState.getActions(); 1597 if ((newActions & PlaybackStateCompat.ACTION_PAUSE) != 0) { 1598 mPlayPauseButton.setVisibility(View.VISIBLE); 1599 } 1600 if ((newActions & PlaybackStateCompat.ACTION_REWIND) != 0 1601 && mMediaType != MEDIA_TYPE_MUSIC) { 1602 if (mRewButton != null) { 1603 mRewButton.setVisibility(View.VISIBLE); 1604 } 1605 } 1606 if ((newActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0 1607 && mMediaType != MEDIA_TYPE_MUSIC) { 1608 if (mFfwdButton != null) { 1609 mFfwdButton.setVisibility(View.VISIBLE); 1610 } 1611 } 1612 if ((newActions & PlaybackStateCompat.ACTION_SEEK_TO) != 0) { 1613 mSeekAvailable = true; 1614 } else { 1615 mSeekAvailable = false; 1616 } 1617 mPlaybackActions = newActions; 1618 } 1619 1620 // Add buttons if custom actions are present. 1621 List<PlaybackStateCompat.CustomAction> customActions = 1622 mPlaybackState.getCustomActions(); 1623 mCustomButtons.removeAllViews(); 1624 if (customActions.size() > 0) { 1625 for (final PlaybackStateCompat.CustomAction action : customActions) { 1626 ImageButton button = new ImageButton(getContext(), 1627 null /* AttributeSet */, 0 /* Style */); 1628 // Refer Constructor with argument (int defStyleRes) of View.java 1629 button.setImageResource(action.getIcon()); 1630 final String actionString = action.getAction().toString(); 1631 button.setOnClickListener(new OnClickListener() { 1632 @Override 1633 public void onClick(View v) { 1634 mControls.sendCustomAction(actionString, action.getExtras()); 1635 setVisibility(View.VISIBLE); 1636 } 1637 }); 1638 mCustomButtons.addView(button); 1639 } 1640 } 1641 } 1642 1643 @Override onMetadataChanged(MediaMetadataCompat metadata)1644 public void onMetadataChanged(MediaMetadataCompat metadata) { 1645 mMetadata = metadata; 1646 updateDuration(); 1647 updateTitle(); 1648 updateAudioMetadata(); 1649 } 1650 1651 @Override onSessionEvent(String event, Bundle extras)1652 public void onSessionEvent(String event, Bundle extras) { 1653 switch (event) { 1654 case EVENT_UPDATE_TRACK_STATUS: 1655 mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT); 1656 // If there is one or more audio tracks, and this information has not been 1657 // reflected into the Settings window yet, automatically check the first track. 1658 // Otherwise, the Audio Track selection will be defaulted to "None". 1659 mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT); 1660 mAudioTrackList = new ArrayList<String>(); 1661 if (mAudioTrackCount > 0) { 1662 for (int i = 0; i < mAudioTrackCount; i++) { 1663 String track = mResources.getString( 1664 R.string.MediaControlView2_audio_track_number_text, i + 1); 1665 mAudioTrackList.add(track); 1666 } 1667 // Change sub text inside the Settings window. 1668 mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK, 1669 mAudioTrackList.get(0)); 1670 } else { 1671 mAudioTrackList.add(mResources.getString( 1672 R.string.MediaControlView2_audio_track_none_text)); 1673 } 1674 if (mVideoTrackCount == 0 && mAudioTrackCount > 0) { 1675 mMediaType = MEDIA_TYPE_MUSIC; 1676 } 1677 1678 mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT); 1679 mSubtitleDescriptionsList = new ArrayList<String>(); 1680 if (mSubtitleTrackCount > 0) { 1681 mSubtitleButton.setVisibility(View.VISIBLE); 1682 mSubtitleButton.setEnabled(true); 1683 mSubtitleDescriptionsList.add(mResources.getString( 1684 R.string.MediaControlView2_subtitle_off_text)); 1685 for (int i = 0; i < mSubtitleTrackCount; i++) { 1686 String track = mResources.getString( 1687 R.string.MediaControlView2_subtitle_track_number_text, i + 1); 1688 mSubtitleDescriptionsList.add(track); 1689 } 1690 } else { 1691 mSubtitleButton.setVisibility(View.GONE); 1692 mSubtitleButton.setEnabled(false); 1693 } 1694 break; 1695 case EVENT_UPDATE_MEDIA_TYPE_STATUS: 1696 boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT); 1697 if (newStatus != mIsAdvertisement) { 1698 mIsAdvertisement = newStatus; 1699 updateLayout(); 1700 } 1701 break; 1702 } 1703 } 1704 } 1705 1706 private class SettingsAdapter extends BaseAdapter { 1707 private List<Integer> mIconIds; 1708 private List<String> mMainTexts; 1709 private List<String> mSubTexts; 1710 SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts, @Nullable List<Integer> iconIds)1711 SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts, 1712 @Nullable List<Integer> iconIds) { 1713 mMainTexts = mainTexts; 1714 mSubTexts = subTexts; 1715 mIconIds = iconIds; 1716 } 1717 updateSubTexts(List<String> subTexts)1718 public void updateSubTexts(List<String> subTexts) { 1719 mSubTexts = subTexts; 1720 notifyDataSetChanged(); 1721 } 1722 getMainText(int position)1723 public String getMainText(int position) { 1724 if (mMainTexts != null) { 1725 if (position < mMainTexts.size()) { 1726 return mMainTexts.get(position); 1727 } 1728 } 1729 return RESOURCE_EMPTY; 1730 } 1731 1732 @Override getCount()1733 public int getCount() { 1734 return (mMainTexts == null) ? 0 : mMainTexts.size(); 1735 } 1736 1737 @Override getItemId(int position)1738 public long getItemId(int position) { 1739 // Auto-generated method stub--does not have any purpose here 1740 return 0; 1741 } 1742 1743 @Override getItem(int position)1744 public Object getItem(int position) { 1745 // Auto-generated method stub--does not have any purpose here 1746 return null; 1747 } 1748 1749 @Override getView(int position, View convertView, ViewGroup container)1750 public View getView(int position, View convertView, ViewGroup container) { 1751 View row; 1752 if (mSizeType == SIZE_TYPE_FULL) { 1753 row = inflateLayout(getContext(), R.layout.full_settings_list_item); 1754 } else { 1755 row = inflateLayout(getContext(), R.layout.embedded_settings_list_item); 1756 } 1757 TextView mainTextView = (TextView) row.findViewById(R.id.main_text); 1758 TextView subTextView = (TextView) row.findViewById(R.id.sub_text); 1759 ImageView iconView = (ImageView) row.findViewById(R.id.icon); 1760 1761 // Set main text 1762 mainTextView.setText(mMainTexts.get(position)); 1763 1764 // Remove sub text and center the main text if sub texts do not exist at all or the sub 1765 // text at this particular position is empty. 1766 if (mSubTexts == null || RESOURCE_EMPTY.equals(mSubTexts.get(position))) { 1767 subTextView.setVisibility(View.GONE); 1768 } else { 1769 // Otherwise, set sub text. 1770 subTextView.setText(mSubTexts.get(position)); 1771 } 1772 1773 // Remove main icon and set visibility to gone if icons are set to null or the icon at 1774 // this particular position is set to RESOURCE_NON_EXISTENT. 1775 if (mIconIds == null || mIconIds.get(position) == RESOURCE_NON_EXISTENT) { 1776 iconView.setVisibility(View.GONE); 1777 } else { 1778 // Otherwise, set main icon. 1779 iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null)); 1780 } 1781 return row; 1782 } 1783 setSubTexts(List<String> subTexts)1784 public void setSubTexts(List<String> subTexts) { 1785 mSubTexts = subTexts; 1786 } 1787 } 1788 1789 private class SubSettingsAdapter extends BaseAdapter { 1790 private List<String> mTexts; 1791 private int mCheckPosition; 1792 SubSettingsAdapter(List<String> texts, int checkPosition)1793 SubSettingsAdapter(List<String> texts, int checkPosition) { 1794 mTexts = texts; 1795 mCheckPosition = checkPosition; 1796 } 1797 getMainText(int position)1798 public String getMainText(int position) { 1799 if (mTexts != null) { 1800 if (position < mTexts.size()) { 1801 return mTexts.get(position); 1802 } 1803 } 1804 return RESOURCE_EMPTY; 1805 } 1806 1807 @Override getCount()1808 public int getCount() { 1809 return (mTexts == null) ? 0 : mTexts.size(); 1810 } 1811 1812 @Override getItemId(int position)1813 public long getItemId(int position) { 1814 // Auto-generated method stub--does not have any purpose here 1815 return 0; 1816 } 1817 1818 @Override getItem(int position)1819 public Object getItem(int position) { 1820 // Auto-generated method stub--does not have any purpose here 1821 return null; 1822 } 1823 1824 @Override getView(int position, View convertView, ViewGroup container)1825 public View getView(int position, View convertView, ViewGroup container) { 1826 View row; 1827 if (mSizeType == SIZE_TYPE_FULL) { 1828 row = inflateLayout(getContext(), R.layout.full_sub_settings_list_item); 1829 } else { 1830 row = inflateLayout(getContext(), R.layout.embedded_sub_settings_list_item); 1831 } 1832 TextView textView = (TextView) row.findViewById(R.id.text); 1833 ImageView checkView = (ImageView) row.findViewById(R.id.check); 1834 1835 textView.setText(mTexts.get(position)); 1836 if (position != mCheckPosition) { 1837 checkView.setVisibility(View.INVISIBLE); 1838 } 1839 return row; 1840 } 1841 setTexts(List<String> texts)1842 public void setTexts(List<String> texts) { 1843 mTexts = texts; 1844 } 1845 setCheckPosition(int checkPosition)1846 public void setCheckPosition(int checkPosition) { 1847 mCheckPosition = checkPosition; 1848 } 1849 } 1850 } 1851