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