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