public class

MediaControlView

extends androidx.media2.widget.BaseLayout

 java.lang.Object

↳ViewGroup

↳androidx.media2.widget.BaseLayout

↳androidx.media2.widget.MediaControlView

Gradle dependencies

compile group: 'androidx.media2', name: 'media2-widget', version: '1.2.1'

  • groupId: androidx.media2
  • artifactId: media2-widget
  • version: 1.2.1

Artifact androidx.media2:media2-widget:1.2.1 it located at Google repository (https://maven.google.com/)

Overview

A View that contains the controls for MediaPlayer. It provides a wide range of buttons that serve the following functions: play/pause, rewind/fast-forward, skip to next/previous, select subtitle track, enter/exit full screen mode, adjust video quality, select audio track, and adjust playback speed.

The easiest way to use a MediaControlView is by creating a VideoView, which will internally create a MediaControlView instance and handle all the commands from buttons inside MediaControlView. For more information, refer to VideoView. It is also possible to create a MediaControlView programmatically and add it to a custom video view. In this case, the app will need to create a MediaSession instance and set its token inside MediaControlView by calling MediaControlView.setSessionToken(SessionToken). Then MediaControlView will create a MediaController and could send commands to the connected session. By default, the buttons inside MediaControlView will not visible unless the corresponding SessionCommand is marked as allowed. For more details, refer to MediaSession.

Currently, MediaControlView animates off-screen in two steps: 1) Title and bottom bars slide up and down respectively and the transport controls fade out, leaving only the progress bar at the bottom of the view. 2) Progress bar slides down off-screen.

In addition, the following customizations are supported: 1) Set focus to the play/pause button by calling MediaControlView.requestPlayButtonFocus(). 2) Set full screen behavior by calling MediaControlView.setOnFullScreenListener(MediaControlView.OnFullScreenListener)

Summary

Constructors
publicMediaControlView(Context context)

publicMediaControlView(Context context, AttributeSet attrs)

publicMediaControlView(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public java.lang.CharSequencegetAccessibilityClassName()

public voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

public booleanonTouchEvent(MotionEvent ev)

public booleanonTrackballEvent(MotionEvent ev)

public voidonVisibilityAggregated(boolean isVisible)

public voidrequestPlayButtonFocus()

Requests focus for the play/pause button.

public voidsetEnabled(boolean enabled)

public voidsetOnFullScreenListener(MediaControlView.OnFullScreenListener l)

Registers a callback to be invoked when the fullscreen mode should be changed.

public voidsetSessionToken(SessionToken token)

Sets MediaSession token to control corresponding MediaSession.

from androidx.media2.widget.BaseLayoutcheckLayoutParams, generateDefaultLayoutParams, generateLayoutParams, generateLayoutParams, onLayout, shouldDelayChildPressedState
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public MediaControlView(Context context)

public MediaControlView(Context context, AttributeSet attrs)

public MediaControlView(Context context, AttributeSet attrs, int defStyleAttr)

Methods

public void setSessionToken(SessionToken token)

Sets MediaSession token to control corresponding MediaSession. It makes it possible to send and receive data between MediaControlView and VideoView.

public void setOnFullScreenListener(MediaControlView.OnFullScreenListener l)

Registers a callback to be invoked when the fullscreen mode should be changed. This needs to be implemented in order to display the fullscreen button.

Parameters:

l: The callback that will be run

public void requestPlayButtonFocus()

Requests focus for the play/pause button.

public java.lang.CharSequence getAccessibilityClassName()

public boolean onTouchEvent(MotionEvent ev)

public boolean onTrackballEvent(MotionEvent ev)

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

public void setEnabled(boolean enabled)

public void onVisibilityAggregated(boolean isVisible)

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.media2.widget;

import static androidx.media2.MediaController.ControllerResult.RESULT_CODE_NOT_SUPPORTED;
import static androidx.media2.MediaController.ControllerResult.RESULT_CODE_SUCCESS;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.drawable.ScaleDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.LinearInterpolator;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.media2.MediaController;
import androidx.media2.MediaItem;
import androidx.media2.MediaMetadata;
import androidx.media2.MediaPlayer;
import androidx.media2.MediaSession;
import androidx.media2.SessionCommand;
import androidx.media2.SessionCommandGroup;
import androidx.media2.SessionPlayer;
import androidx.media2.SessionToken;
import androidx.media2.UriMediaItem;
import androidx.mediarouter.app.MediaRouteButton;
import androidx.mediarouter.media.MediaRouteSelector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;

/**
 * A View that contains the controls for {@link MediaPlayer}.
 * It provides a wide range of buttons that serve the following functions: play/pause,
 * rewind/fast-forward, skip to next/previous, select subtitle track, enter/exit full screen mode,
 * adjust video quality, select audio track, and adjust playback speed.
 * <p>
 * The easiest way to use a MediaControlView is by creating a {@link VideoView}, which will
 * internally create a MediaControlView instance and handle all the commands from buttons inside
 * MediaControlView. For more information, refer to {@link VideoView}.
 *
 * It is also possible to create a MediaControlView programmatically and add it to a custom video
 * view. In this case, the app will need to create a {@link MediaSession} instance and set
 * {@link SessionToken its token} inside MediaControlView by calling
 * {@link #setSessionToken(SessionToken)}. Then MediaControlView will create a
 * {@link MediaController} and could send commands to the connected {@link MediaSession session}.
 * By default, the buttons inside MediaControlView will not visible unless the corresponding
 * {@link SessionCommand} is marked as allowed. For more details, refer to {@link MediaSession}.
 * <p>
 * Currently, MediaControlView animates off-screen in two steps:
 *   1) Title and bottom bars slide up and down respectively and the transport controls fade out,
 *      leaving only the progress bar at the bottom of the view.
 *   2) Progress bar slides down off-screen.
 * <p>
 * In addition, the following customizations are supported:
 * 1) Set focus to the play/pause button by calling {@link #requestPlayButtonFocus()}.
 * 2) Set full screen behavior by calling {@link #setOnFullScreenListener(OnFullScreenListener)}
 *
 */
@RequiresApi(19) // TODO correct minSdk API use incompatibilities and remove before release.
public class MediaControlView extends BaseLayout {
    private static final String TAG = "MediaControlView";
    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
    static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
    static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
    static final String KEY_SUBTITLE_TRACK_LANGUAGE_LIST = "SubtitleTrackLanguageList";
    static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
    static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
    static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
    static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
    static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
    static final String EVENT_UPDATE_SUBTITLE_SELECTED = "UpdateSubtitleSelected";
    static final String EVENT_UPDATE_SUBTITLE_DESELECTED = "UpdateSubtitleDeselected";

    // String for sending command to show subtitle to MediaSession.
    static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
    // String for sending command to hide subtitle to MediaSession.
    static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
    // String for sending command to select audio track to MediaSession.
    static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";

    private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
    private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
    private static final int SETTINGS_MODE_SUBTITLE_TRACK = 2;
    private static final int SETTINGS_MODE_VIDEO_QUALITY = 3;
    private static final int SETTINGS_MODE_MAIN = 4;
    private static final int PLAYBACK_SPEED_1x_INDEX = 3;

    private static final int MEDIA_TYPE_DEFAULT = 0;
    private static final int MEDIA_TYPE_MUSIC = 1;
    private static final int MEDIA_TYPE_ADVERTISEMENT = 2;

    private static final int BOTTOM_BAR_RIGHT_VIEW_MAX_ICON_NUM_DEFAULT = 3;
    private static final int BOTTOM_BAR_RIGHT_VIEW_MAX_ICON_NUM_MUSIC = 2;

    private static final int SIZE_TYPE_EMBEDDED = 0;
    private static final int SIZE_TYPE_FULL = 1;
    private static final int SIZE_TYPE_MINIMAL = 2;

    // Int for defining the UX state where all the views (TitleBar, ProgressBar, BottomBar) are
    // all visible.
    private static final int UX_STATE_ALL_VISIBLE = 0;
    // Int for defining the UX state where only the ProgressBar view is visible.
    private static final int UX_STATE_ONLY_PROGRESS_VISIBLE = 1;
    // Int for defining the UX state where none of the views are visible.
    private static final int UX_STATE_NONE_VISIBLE = 2;
    // Int for defining the UX state where the views are being animated (shown or hidden).
    private static final int UX_STATE_ANIMATING = 3;

    private static final long DEFAULT_SHOW_CONTROLLER_INTERVAL_MS = 2000;
    private static final long DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
    private static final long REWIND_TIME_MS = 10000;
    private static final long FORWARD_TIME_MS = 30000;
    private static final long AD_SKIP_WAIT_TIME_MS = 5000;
    private static final long HIDE_TIME_MS = 250;
    private static final long SHOW_TIME_MS = 250;
    private static final int MAX_PROGRESS = 1000;
    private static final int MAX_SCALE_LEVEL = 10000;
    private static final int RESOURCE_NON_EXISTENT = -1;
    private static final int SEEK_POSITION_NOT_SET = -1;
    private static final String RESOURCE_EMPTY = "";

    Resources mResources;
    Controller mController;
    OnFullScreenListener mOnFullScreenListener;
    private AccessibilityManager mAccessibilityManager;
    private WindowManager mWindowManager;
    private int mPrevWidth;
    private int mPrevOrientation;
    private int mOriginalLeftBarWidth;
    private int mMaxTimeViewWidth;
    private int mEmbeddedSettingsItemWidth;
    private int mFullSettingsItemWidth;
    private int mSettingsItemHeight;
    private int mSettingsWindowMargin;
    private int mIconSize;
    int mVideoTrackCount;
    int mAudioTrackCount;
    int mSubtitleTrackCount;
    int mSettingsMode;
    int mSelectedSubtitleTrackIndex;
    int mSelectedAudioTrackIndex;
    int mSelectedVideoQualityIndex;
    int mSelectedSpeedIndex;
    int mMediaType;
    // TODO: Add lock for accessing mSizeType and mUxState (b/111862062)
    int mSizeType;
    int mUxState;
    long mDuration;
    long mPlaybackActions;
    long mShowControllerIntervalMs;
    // TODO: Add lock for accessing mCurrentSeekPosition and mNextSeekPosition (b/111862062)
    long mCurrentSeekPosition;
    long mNextSeekPosition;
    boolean mDragging;
    boolean mIsFullScreen;
    boolean mOverflowIsShowing;
    boolean mIsStopped;
    boolean mSeekAvailable;
    boolean mIsAdvertisement;
    boolean mNeedToHideBars;
    boolean mWasPlaying;

    // Relating to Title Bar View
    private ViewGroup mRoot;
    private View mTitleBar;
    private TextView mTitleView;
    private View mAdExternalLink;
    private ImageButton mBackButton;
    private MediaRouteButton mRouteButton;
    private MediaRouteSelector mRouteSelector;

    // Relating to Center View
    private ViewGroup mCenterView;
    View mTransportControls;
    ImageButton mPlayPauseButton;
    ImageButton mFfwdButton;
    ImageButton mRewButton;
    // TODO: Disable Next/Previous buttons when the current item does not have a next/previous
    // item in the playlist. (b/119159436)
    private ImageButton mNextButton;
    private ImageButton mPrevButton;

    // Relating to Minimal Size Fullscreen View
    private LinearLayout mMinimalSizeFullScreenView;

    // Relating to Progress Bar View
    View mProgressBar;
    ProgressBar mProgress;
    private View mProgressBuffer;

    // Relating to Bottom Bar View
    private ViewGroup mBottomBar;

    // Relating to Bottom Bar Left View
    private ViewGroup mBottomBarLeftView;
    private ViewGroup mTimeView;
    private TextView mEndTime;
    TextView mCurrentTime;
    private TextView mAdSkipView;
    private StringBuilder mFormatBuilder;
    private Formatter mFormatter;

    // Relating to Bottom Bar Right View
    private ViewGroup mBottomBarRightView;
    ViewGroup mBasicControls;
    ViewGroup mExtraControls;
    ViewGroup mCustomButtons;
    ImageButton mSubtitleButton;
    ImageButton mFullScreenButton;
    ImageButton mOverflowShowButton;
    ImageButton mOverflowHideButton;
    private ImageButton mVideoQualityButton;
    private ImageButton mSettingsButton;
    private TextView mAdRemainingView;

    // Relating to Settings List View
    private ListView mSettingsListView;
    private PopupWindow mSettingsWindow;
    SettingsAdapter mSettingsAdapter;
    SubSettingsAdapter mSubSettingsAdapter;
    private List<String> mSettingsMainTextsList;
    List<String> mSettingsSubTextsList;
    private List<Integer> mSettingsIconIdsList;
    List<String> mSubtitleDescriptionsList;
    List<String> mAudioTrackList;
    List<String> mVideoQualityList;
    List<String> mPlaybackSpeedTextList;
    List<Integer> mPlaybackSpeedMultBy100List;
    int mCustomPlaybackSpeedIndex;

    AnimatorSet mHideMainBarsAnimator;
    AnimatorSet mHideProgressBarAnimator;
    AnimatorSet mHideAllBarsAnimator;
    AnimatorSet mShowMainBarsAnimator;
    AnimatorSet mShowAllBarsAnimator;
    ValueAnimator mOverflowShowAnimator;
    ValueAnimator mOverflowHideAnimator;

    public MediaControlView(@NonNull Context context) {
        this(context, null);
    }

    public MediaControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MediaControlView(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mResources = context.getResources();
        mController = new Controller();
        // Inflate MediaControlView from XML
        mRoot = makeControllerView();
        addView(mRoot);
        mShowControllerIntervalMs = DEFAULT_SHOW_CONTROLLER_INTERVAL_MS;
        mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                Context.ACCESSIBILITY_SERVICE);
    }

    /**
     * Sets MediaSession token to control corresponding MediaSession. It makes it possible to
     * send and receive data between MediaControlView and VideoView.
     */
    public void setSessionToken(@NonNull SessionToken token) {
        mController.setSessionToken(token);
        if (mController.hasMetadata()) {
            updateMetadata();
        }
    }

    /**
     * Registers a callback to be invoked when the fullscreen mode should be changed.
     * This needs to be implemented in order to display the fullscreen button.
     * @param l The callback that will be run
     */
    public void setOnFullScreenListener(@NonNull OnFullScreenListener l) {
        mOnFullScreenListener = l;
        mFullScreenButton.setVisibility(View.VISIBLE);
    }

    /**
     *  Requests focus for the play/pause button.
     */
    public void requestPlayButtonFocus() {
        if (mPlayPauseButton != null) {
            mPlayPauseButton.requestFocus();
        }
    }

    /**
     * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
     * Application should handle the fullscreen mode accordingly.
     */
    public interface OnFullScreenListener {
        /**
         * Called to indicate a fullscreen mode change.
         */
        void onFullScreen(@NonNull View view, boolean fullScreen);
    }

    @Override
    public CharSequence getAccessibilityClassName() {
        return MediaControlView.class.getName();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            if (mMediaType != MEDIA_TYPE_MUSIC || mSizeType != SIZE_TYPE_FULL) {
                toggleMediaControlViewVisibility();
            }
        }
        return true;
    }

    @Override
    public boolean onTrackballEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            if (mMediaType != MEDIA_TYPE_MUSIC || mSizeType != SIZE_TYPE_FULL) {
                toggleMediaControlViewVisibility();
            }
        }
        return true;
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Update layout when this view's width changes in order to avoid any UI overlap between
        // transport controls.
        if (mPrevWidth != getMeasuredWidth()) {
            // The following view may not have been initialized yet.
            if (mTimeView.getWidth() == 0) {
                return;
            }

            // Update layout if necessary
            int currWidth = getMeasuredWidth();
            int currHeight = getMeasuredHeight();
            Point screenSize = new Point();
            mWindowManager.getDefaultDisplay().getSize(screenSize);
            if (mMediaType == MEDIA_TYPE_DEFAULT) {
                updateLayout(BOTTOM_BAR_RIGHT_VIEW_MAX_ICON_NUM_DEFAULT, currWidth,
                        currHeight, screenSize.x, screenSize.y);
            } else if (mMediaType == MEDIA_TYPE_MUSIC) {
                updateLayout(BOTTOM_BAR_RIGHT_VIEW_MAX_ICON_NUM_MUSIC, currWidth,
                        currHeight, screenSize.x, screenSize.y);
            }
            mPrevWidth = currWidth;

            // By default, show all bars and hide settings window and overflow view when view size
            // is changed.
            showAllBars();
            hideSettingsAndOverflow();
        }

        // By default, show all bars and hide settings window and overflow view when view
        // orientation is changed.
        int currOrientation = retrieveOrientation();
        if (currOrientation != mPrevOrientation) {
            showAllBars();
            hideSettingsAndOverflow();
            mPrevOrientation = currOrientation;
        }
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);

        if (mPlayPauseButton != null) {
            mPlayPauseButton.setEnabled(enabled);
        }
        if (mFfwdButton != null) {
            mFfwdButton.setEnabled(enabled);
        }
        if (mRewButton != null) {
            mRewButton.setEnabled(enabled);
        }
        if (mNextButton != null) {
            mNextButton.setEnabled(enabled);
        }
        if (mPrevButton != null) {
            mPrevButton.setEnabled(enabled);
        }
        if (mProgress != null) {
            mProgress.setEnabled(enabled);
        }
        if (mSubtitleButton != null) {
            mSubtitleButton.setEnabled(enabled);
        }
        if (mFullScreenButton != null) {
            mFullScreenButton.setEnabled(enabled);
        }
        if (mOverflowShowButton != null) {
            mOverflowShowButton.setEnabled(enabled);
        }
        if (mOverflowHideButton != null) {
            mOverflowHideButton.setEnabled(enabled);
        }
        if (mVideoQualityButton != null) {
            mVideoQualityButton.setEnabled(enabled);
        }
        if (mSettingsButton != null) {
            mSettingsButton.setEnabled(enabled);
        }
        if (mBackButton != null) {
            mBackButton.setEnabled(enabled);
        }
        if (mRouteButton != null) {
            mRouteButton.setEnabled(enabled);
        }
        disableUnsupportedButtons();
    }

    @Override
    public void onVisibilityAggregated(boolean isVisible) {
        super.onVisibilityAggregated(isVisible);

        if (isVisible) {
            disableUnsupportedButtons();
            removeCallbacks(mUpdateProgress);
            post(mUpdateProgress);
        } else {
            removeCallbacks(mUpdateProgress);
        }
    }

    void setRouteSelector(MediaRouteSelector selector) {
        mRouteSelector = selector;
        if (mRouteSelector != null && !mRouteSelector.isEmpty()) {
            mRouteButton.setRouteSelector(selector);
            mRouteButton.setVisibility(View.VISIBLE);
        } else {
            mRouteButton.setRouteSelector(MediaRouteSelector.EMPTY);
            mRouteButton.setVisibility(View.GONE);
        }
    }

    void setShowControllerInterval(long interval) {
        mShowControllerIntervalMs = interval;
    }

    ///////////////////////////////////////////////////
    // Protected or private methods
    ///////////////////////////////////////////////////

    /**
     * Create the view that holds the widgets that control playback.
     * Derived classes can override this to create their own.
     *
     * @return The controller view.
     */
    private ViewGroup makeControllerView() {
        ViewGroup root = (ViewGroup) inflateLayout(getContext(), R.layout.media_controller);
        initControllerView(root);
        return root;
    }

    static View inflateLayout(Context context, int resId) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        return inflater.inflate(resId, null);
    }

    @SuppressWarnings("deprecation")
    private void initControllerView(ViewGroup v) {
        mWindowManager = (WindowManager) getContext().getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
        mIconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_size);

        // Relating to Title Bar View
        mTitleBar = v.findViewById(R.id.title_bar);
        mTitleView = v.findViewById(R.id.title_text);
        mAdExternalLink = v.findViewById(R.id.ad_external_link);
        mBackButton = v.findViewById(R.id.back);
        if (mBackButton != null) {
            mBackButton.setOnClickListener(mBackListener);
            mBackButton.setVisibility(View.GONE);
        }
        mRouteButton = v.findViewById(R.id.cast);

        // Relating to Center View
        mCenterView = v.findViewById(R.id.center_view);
        mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
        mCenterView.addView(mTransportControls);

        // Relating to Minimal Size FullScreen View. This view is visible only when the current
        // size type is Minimal and it is a view that stretches from left to right end
        // and helps locate the fullscreen button at the right end of the screen.
        mMinimalSizeFullScreenView = (LinearLayout) v.findViewById(R.id.minimal_fullscreen_view);
        LinearLayout.LayoutParams params =
                (LinearLayout.LayoutParams) mMinimalSizeFullScreenView.getLayoutParams();
        int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_size);
        params.setMargins(0, iconSize * (-1), 0, 0);
        mMinimalSizeFullScreenView.setLayoutParams(params);
        mMinimalSizeFullScreenView.setVisibility(View.GONE);

        // Relating to Progress Bar View
        mProgressBar = v.findViewById(R.id.progress_bar);
        mProgress = v.findViewById(R.id.progress);
        if (mProgress != null) {
            if (mProgress instanceof SeekBar) {
                SeekBar seeker = (SeekBar) mProgress;
                seeker.setOnSeekBarChangeListener(mSeekListener);
                seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
                seeker.setThumbOffset(0);
            }
            mProgress.setMax(MAX_PROGRESS);
        }
        mProgressBuffer = v.findViewById(R.id.progress_buffer);
        mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
        mNextSeekPosition = SEEK_POSITION_NOT_SET;

        // Relating to Bottom Bar View
        mBottomBar = v.findViewById(R.id.bottom_bar);

        // Relating to Bottom Bar Left View
        mBottomBarLeftView = v.findViewById(R.id.bottom_bar_left);
        mTimeView = v.findViewById(R.id.time);
        mEndTime = v.findViewById(R.id.time_end);
        mCurrentTime = v.findViewById(R.id.time_current);
        mAdSkipView = v.findViewById(R.id.ad_skip_time);
        mFormatBuilder = new StringBuilder();
        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());

        // Relating to Bottom Bar Right View
        mBasicControls = v.findViewById(R.id.basic_controls);
        mExtraControls = v.findViewById(R.id.extra_controls);
        mCustomButtons = v.findViewById(R.id.custom_buttons);
        mSubtitleButton = v.findViewById(R.id.subtitle);
        if (mSubtitleButton != null) {
            mSubtitleButton.setOnClickListener(mSubtitleListener);
        }
        mFullScreenButton = v.findViewById(R.id.fullscreen);
        if (mFullScreenButton != null) {
            mFullScreenButton.setOnClickListener(mFullScreenListener);
        }
        mOverflowShowButton = v.findViewById(R.id.overflow_show);
        if (mOverflowShowButton != null) {
            mOverflowShowButton.setOnClickListener(mOverflowShowListener);
        }
        mOverflowHideButton = v.findViewById(R.id.overflow_hide);
        if (mOverflowHideButton != null) {
            mOverflowHideButton.setOnClickListener(mOverflowHideListener);
        }
        mSettingsButton = v.findViewById(R.id.settings);
        if (mSettingsButton != null) {
            mSettingsButton.setOnClickListener(mSettingsButtonListener);
        }
        mVideoQualityButton = v.findViewById(R.id.video_quality);
        if (mVideoQualityButton != null) {
            mVideoQualityButton.setOnClickListener(mVideoQualityListener);
        }
        mAdRemainingView = v.findViewById(R.id.ad_remaining);

        // Relating to Settings List View
        initializeSettingsLists();
        mSettingsListView = (ListView) inflateLayout(getContext(), R.layout.settings_list);
        mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
                mSettingsIconIdsList);
        mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
        mSettingsListView.setAdapter(mSettingsAdapter);
        mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);

        mEmbeddedSettingsItemWidth = mResources.getDimensionPixelSize(
                R.dimen.mcv2_embedded_settings_width);
        mFullSettingsItemWidth = mResources.getDimensionPixelSize(R.dimen.mcv2_full_settings_width);
        mSettingsItemHeight = mResources.getDimensionPixelSize(
                R.dimen.mcv2_settings_height);
        mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
                R.dimen.mcv2_settings_offset);
        mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
                LayoutParams.WRAP_CONTENT, true);
        mSettingsWindow.setOnDismissListener(mSettingsDismissListener);

        int titleBarTranslateY =
                (-1) * mResources.getDimensionPixelSize(R.dimen.mcv2_title_bar_height);
        int bottomBarHeight = mResources.getDimensionPixelSize(R.dimen.mcv2_bottom_bar_height);
        int progressThumbHeight = mResources.getDimensionPixelSize(
                R.dimen.mcv2_custom_progress_thumb_size);
        int progressBarHeight = mResources.getDimensionPixelSize(
                R.dimen.mcv2_custom_progress_max_size);
        int bottomBarTranslateY = bottomBarHeight + progressThumbHeight / 2 - progressBarHeight / 2;

        ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
        fadeOutAnimator.setInterpolator(new LinearInterpolator());
        fadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float alpha = (float) animation.getAnimatedValue();
                SeekBar seekBar = (SeekBar) mProgress;
                if (mSizeType != SIZE_TYPE_MINIMAL) {
                    ScaleDrawable thumb = (ScaleDrawable) seekBar.getThumb();
                    if (thumb != null) {
                        thumb.setLevel((int) (MAX_SCALE_LEVEL * alpha));
                    }
                }

                mTransportControls.setAlpha(alpha);
                if (alpha == 0.0f) {
                    mTransportControls.setVisibility(View.GONE);
                } else if (alpha == 1.0f) {
                    setEnabled(false);
                }
                if (mSizeType == SIZE_TYPE_MINIMAL) {
                    mFullScreenButton.setAlpha(alpha);
                    mProgressBar.setAlpha(alpha);
                    if (alpha == 0.0f) {
                        if (mOnFullScreenListener != null) {
                            mFullScreenButton.setVisibility(View.GONE);
                        }
                        mProgressBar.setVisibility(View.GONE);
                    }
                }
            }
        });

        ValueAnimator fadeInAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        fadeInAnimator.setInterpolator(new LinearInterpolator());
        fadeInAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float alpha = (float) animation.getAnimatedValue();
                SeekBar seekBar = (SeekBar) mProgress;
                if (mSizeType != SIZE_TYPE_MINIMAL) {
                    ScaleDrawable thumb = (ScaleDrawable) seekBar.getThumb();
                    if (thumb != null) {
                        thumb.setLevel((int) (MAX_SCALE_LEVEL * alpha));
                    }
                }

                mTransportControls.setAlpha(alpha);
                if (alpha == 0.0f) {
                    mTransportControls.setVisibility(View.VISIBLE);
                } else if (alpha == 1.0f) {
                    setEnabled(true);
                }
                if (mSizeType == SIZE_TYPE_MINIMAL) {
                    mFullScreenButton.setAlpha(alpha);
                    mProgressBar.setAlpha(alpha);
                    if (alpha == 0.0f) {
                        if (mOnFullScreenListener != null) {
                            mFullScreenButton.setVisibility(View.VISIBLE);
                        }
                        mProgressBar.setVisibility(View.VISIBLE);
                    }
                }
            }
        });

        mHideMainBarsAnimator = new AnimatorSet();
        mHideMainBarsAnimator
                .play(ObjectAnimator.ofFloat(mTitleBar, "translationY",
                        0, titleBarTranslateY))
                .with(ObjectAnimator.ofFloat(mBottomBar, "translationY",
                        0, bottomBarTranslateY))
                .with(ObjectAnimator.ofFloat(mProgressBar, "translationY",
                        0, bottomBarTranslateY))
                .with(fadeOutAnimator);
        mHideMainBarsAnimator.setDuration(HIDE_TIME_MS);
        mHideMainBarsAnimator.getChildAnimations().get(0).addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        setEnabled(false);
                        mUxState = UX_STATE_ANIMATING;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        setEnabled(true);
                        mUxState = UX_STATE_ONLY_PROGRESS_VISIBLE;
                    }
                });

        mHideProgressBarAnimator = new AnimatorSet();
        mHideProgressBarAnimator
                .play(ObjectAnimator.ofFloat(mBottomBar, "translationY",
                        bottomBarTranslateY, bottomBarTranslateY + progressBarHeight))
                .with(ObjectAnimator.ofFloat(mProgressBar, "translationY",
                        bottomBarTranslateY, bottomBarTranslateY + progressBarHeight));
        mHideProgressBarAnimator.setDuration(HIDE_TIME_MS);
        mHideProgressBarAnimator.getChildAnimations().get(0).addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        setEnabled(false);
                        mUxState = UX_STATE_ANIMATING;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        setEnabled(true);
                        mUxState = UX_STATE_NONE_VISIBLE;
                    }
                });

        mHideAllBarsAnimator = new AnimatorSet();
        mHideAllBarsAnimator
                .play(ObjectAnimator.ofFloat(mTitleBar, "translationY",
                        0, titleBarTranslateY))
                .with(ObjectAnimator.ofFloat(mBottomBar, "translationY",
                        0, bottomBarTranslateY + progressBarHeight))
                .with(ObjectAnimator.ofFloat(mProgressBar, "translationY",
                        0, bottomBarTranslateY + progressBarHeight))
                .with(fadeOutAnimator);
        mHideAllBarsAnimator.setDuration(HIDE_TIME_MS);
        mHideAllBarsAnimator.getChildAnimations().get(0).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                setEnabled(false);
                mUxState = UX_STATE_ANIMATING;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                setEnabled(true);
                mUxState = UX_STATE_NONE_VISIBLE;
            }
        });

        mShowMainBarsAnimator = new AnimatorSet();
        mShowMainBarsAnimator
                .play(ObjectAnimator.ofFloat(mTitleBar, "translationY",
                        titleBarTranslateY, 0))
                .with(ObjectAnimator.ofFloat(mBottomBar, "translationY",
                        bottomBarTranslateY, 0))
                .with(ObjectAnimator.ofFloat(mProgressBar, "translationY",
                        bottomBarTranslateY, 0))
                .with(fadeInAnimator);
        mShowMainBarsAnimator.setDuration(SHOW_TIME_MS);
        mShowMainBarsAnimator.getChildAnimations().get(0).addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        setEnabled(false);
                        mUxState = UX_STATE_ANIMATING;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        setEnabled(true);
                        mUxState = UX_STATE_ALL_VISIBLE;
                    }
                });

        mShowAllBarsAnimator = new AnimatorSet();
        mShowAllBarsAnimator
                .play(ObjectAnimator.ofFloat(mTitleBar, "translationY",
                        titleBarTranslateY, 0))
                .with(ObjectAnimator.ofFloat(mBottomBar, "translationY",
                        bottomBarTranslateY + progressBarHeight, 0))
                .with(ObjectAnimator.ofFloat(mProgressBar, "translationY",
                        bottomBarTranslateY + progressBarHeight, 0))
                .with(fadeInAnimator);
        mShowAllBarsAnimator.setDuration(SHOW_TIME_MS);
        mShowAllBarsAnimator.getChildAnimations().get(0).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                setEnabled(false);
                mUxState = UX_STATE_ANIMATING;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                setEnabled(true);
                mUxState = UX_STATE_ALL_VISIBLE;
            }
        });

        mOverflowShowAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mOverflowShowAnimator.setDuration(SHOW_TIME_MS);
        mOverflowShowAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animateOverflow(animation);
            }
        });
        mOverflowShowAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                mExtraControls.setVisibility(View.VISIBLE);
                mOverflowShowButton.setVisibility(View.GONE);
                mOverflowHideButton.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mBasicControls.setVisibility(View.GONE);

                if (mSizeType == SIZE_TYPE_FULL && mMediaType == MEDIA_TYPE_DEFAULT) {
                    mFfwdButton.setVisibility(View.GONE);
                }
            }
        });

        mOverflowHideAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
        mOverflowHideAnimator.setDuration(SHOW_TIME_MS);
        mOverflowHideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animateOverflow(animation);
            }
        });
        mOverflowHideAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                mBasicControls.setVisibility(View.VISIBLE);
                mOverflowShowButton.setVisibility(View.VISIBLE);
                mOverflowHideButton.setVisibility(View.GONE);

                if (mSizeType == SIZE_TYPE_FULL && mMediaType == MEDIA_TYPE_DEFAULT) {
                    mFfwdButton.setVisibility(View.VISIBLE);
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mExtraControls.setVisibility(View.GONE);
            }
        });
    }

    /**
     * Disable pause or seek buttons if the stream cannot be paused or seeked.
     * This requires the control interface to be a MediaPlayerControlExt
     * TODO: b/110905302
     */
    private void disableUnsupportedButtons() {
        try {
            if (mPlayPauseButton != null && !mController.canPause()) {
                mPlayPauseButton.setEnabled(false);
            }
            if (mRewButton != null && !mController.canSeekBackward()) {
                mRewButton.setEnabled(false);
            }
            if (mFfwdButton != null && !mController.canSeekForward()) {
                mFfwdButton.setEnabled(false);
            }
            if (mProgress != null && !mController.canSeekBackward()
                    && !mController.canSeekForward()) {
                mProgress.setEnabled(false);
            }
        } catch (IncompatibleClassChangeError ex) {
            // We were given an old version of the interface, that doesn't have
            // the canPause/canSeekXYZ methods. This is OK, it just means we
            // assume the media can be paused and seeked, and so we don't disable
            // the buttons.
        }
    }

    final Runnable mUpdateProgress = new Runnable() {
        @Override
        public void run() {
            boolean isShowing = getVisibility() == View.VISIBLE;
            if (!mDragging && isShowing && mController.isPlaying()) {
                long pos = setProgress();
                postDelayed(mUpdateProgress,
                        DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
            }
        }
    };

    String stringForTime(long timeMs) {
        long totalSeconds = timeMs / 1000;

        long seconds = totalSeconds % 60;
        long minutes = (totalSeconds / 60) % 60;
        long hours = totalSeconds / 3600;

        mFormatBuilder.setLength(0);
        if (hours > 0) {
            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
        } else {
            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
        }
    }

    long setProgress() {
        int positionOnProgressBar = 0;
        long currentPosition = mController.getCurrentPosition();
        if (currentPosition > mDuration) {
            currentPosition = mDuration;
        }
        if (mDuration > 0) {
            positionOnProgressBar = (int) (MAX_PROGRESS * currentPosition / mDuration);
        }
        if (mProgress != null && currentPosition != mDuration) {
            mProgress.setProgress(positionOnProgressBar);
            // If the media is a local file, there is no need to set a buffer, so set secondary
            // progress to maximum.
            if (mController.getBufferPercentage() < 0) {
                mProgress.setSecondaryProgress(MAX_PROGRESS);
            } else {
                mProgress.setSecondaryProgress((int) mController.getBufferPercentage() * 10);
            }
        }

        if (mEndTime != null) {
            mEndTime.setText(stringForTime(mDuration));
        }
        if (mCurrentTime != null) {
            mCurrentTime.setText(stringForTime(currentPosition));
        }

        if (mIsAdvertisement) {
            // Update the remaining number of seconds until the first 5 seconds of the
            // advertisement.
            if (mAdSkipView != null) {
                if (currentPosition <= AD_SKIP_WAIT_TIME_MS) {
                    if (mAdSkipView.getVisibility() == View.GONE) {
                        mAdSkipView.setVisibility(View.VISIBLE);
                    }
                    String skipTimeText = mResources.getString(
                            R.string.MediaControlView_ad_skip_wait_time,
                            ((AD_SKIP_WAIT_TIME_MS - currentPosition) / 1000 + 1));
                    mAdSkipView.setText(skipTimeText);
                } else {
                    if (mAdSkipView.getVisibility() == View.VISIBLE) {
                        mAdSkipView.setVisibility(View.GONE);
                        mNextButton.setEnabled(true);
                        mNextButton.clearColorFilter();
                    }
                }
            }
            // Update the remaining number of seconds of the advertisement.
            if (mAdRemainingView != null) {
                long remainingTime =
                        (mDuration - currentPosition < 0) ? 0 : (mDuration - currentPosition);
                String remainingTimeText = mResources.getString(
                        R.string.MediaControlView_ad_remaining_time,
                        stringForTime(remainingTime));
                mAdRemainingView.setText(remainingTimeText);
            }
        }
        return currentPosition;
    }

    void togglePausePlayState() {
        if (mController.isPlaying()) {
            mController.pause();
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_play_circle_filled));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_play_button_desc));
        } else {
            if (mIsStopped) {
                mController.seekTo(0);
            }
            mController.play();
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_pause_circle_filled));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_pause_button_desc));
        }
    }

    private void toggleMediaControlViewVisibility() {
        if (shouldNotHideBars() || mShowControllerIntervalMs == 0
                || mUxState == UX_STATE_ANIMATING) {
            return;
        }
        removeCallbacks(mHideMainBars);
        removeCallbacks(mHideProgressBar);

        switch (mUxState) {
            case UX_STATE_NONE_VISIBLE:
                post(mShowAllBars);
                break;
            case UX_STATE_ONLY_PROGRESS_VISIBLE:
                post(mShowMainBars);
                break;
            case UX_STATE_ALL_VISIBLE:
                post(mHideAllBars);
                break;
        }
    }

    private final Runnable mShowAllBars = new Runnable() {
        @Override
        public void run() {
            mShowAllBarsAnimator.start();
            if (mController.isPlaying()) {
                postDelayed(mHideMainBars, mShowControllerIntervalMs);
            }
        }
    };

    private final Runnable mShowMainBars = new Runnable() {
        @Override
        public void run() {
            mShowMainBarsAnimator.start();
            postDelayed(mHideMainBars, mShowControllerIntervalMs);
        }
    };

    private final Runnable mHideAllBars = new Runnable() {
        @Override
        public void run() {
            if (shouldNotHideBars()) {
                return;
            }
            mHideAllBarsAnimator.start();
        }
    };

    Runnable mHideMainBars = new Runnable() {
        @Override
        public void run() {
            if (!mController.isPlaying() || shouldNotHideBars()) {
                return;
            }
            mHideMainBarsAnimator.start();
            postDelayed(mHideProgressBar, mShowControllerIntervalMs);
        }
    };

    final Runnable mHideProgressBar = new Runnable() {
        @Override
        public void run() {
            if (!mController.isPlaying() || shouldNotHideBars()) {
                return;
            }
            mHideProgressBarAnimator.start();
        }
    };

    // There are two scenarios that can trigger the seekbar listener to trigger:
    //
    // The first is the user using the touchpad to adjust the position of the
    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
    // We're setting the field "mDragging" to true for the duration of the dragging
    // session to avoid jumps in the position in case of ongoing playback.
    //
    // The second scenario involves the user operating the scroll ball, in this
    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
    // we will simply apply the updated position without suspending regular updates.
    private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
        @Override
        public void onStartTrackingTouch(SeekBar bar) {
            if (!mSeekAvailable) {
                return;
            }

            mDragging = true;

            // By removing these pending progress messages we make sure
            // that a) we won't update the progress while the user adjusts
            // the seekbar and b) once the user is done dragging the thumb
            // we will post one of these messages to the queue again and
            // this ensures that there will be exactly one message queued up.
            removeCallbacks(mUpdateProgress);
            removeCallbacks(mHideMainBars);
            removeCallbacks(mHideProgressBar);

            // Check if playback is currently stopped. In this case, update the pause button to
            // show the play image instead of the replay image.
            if (mIsStopped) {
                updateForStoppedState(false);
            }

            if (isCurrentMediaItemFromNetwork() && mController.isPlaying()) {
                mWasPlaying = true;
                mController.pause();
            }
        }

        @Override
        public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
            if (!mSeekAvailable) {
                return;
            }
            if (!fromUser) {
                // We're not interested in programmatically generated changes to
                // the progress bar's position.
                return;
            }
            // Check if progress bar is being dragged since this method may be called after
            // onStopTrackingTouch() is called.
            if (mDragging && mDuration > 0) {
                long newPosition = ((mDuration * progress) / MAX_PROGRESS);
                // Do not seek if the current media item has a http scheme URL to improve seek
                // performance.
                boolean shouldSeekNow = !isCurrentMediaItemFromNetwork();
                seekTo(newPosition, shouldSeekNow);
            }
        }

        @Override
        public void onStopTrackingTouch(SeekBar bar) {
            if (!mSeekAvailable) {
                return;
            }
            mDragging = false;

            long latestSeekPosition = getLatestSeekPosition();
            // Reset existing seek positions since we only need to seek to the latest position.
            if (isCurrentMediaItemFromNetwork()) {
                mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
                mNextSeekPosition = SEEK_POSITION_NOT_SET;
            }
            seekTo(latestSeekPosition, true);

            if (mWasPlaying) {
                mWasPlaying = false;
                mController.play();
            }
        }
    };

    private final OnClickListener mPlayPauseListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();
            togglePausePlayState();
        }
    };

    private final OnClickListener mRewListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();
            removeCallbacks(mUpdateProgress);

            long latestSeekPosition = getLatestSeekPosition();
            if (mIsStopped && mDuration != 0) {
                // If the media is currently stopped, rewinding will start the media from the
                // beginning. Instead, seek to 10 seconds before the end of the media.
                seekTo(mDuration - REWIND_TIME_MS, true);
                updateForStoppedState(false);
            } else {
                seekTo(Math.max(latestSeekPosition - REWIND_TIME_MS, 0), true);
            }
        }
    };

    private final OnClickListener mFfwdListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();
            removeCallbacks(mUpdateProgress);

            long latestSeekPosition = getLatestSeekPosition();
            seekTo(Math.min(latestSeekPosition + FORWARD_TIME_MS, mDuration), true);
            if (latestSeekPosition + FORWARD_TIME_MS >= mDuration) {
                // If the media is currently paused, fast-forwarding beyond the duration value will
                // not return a callback that updates the play/pause and ffwd buttons. Thus,
                // update the buttons manually here.
                updateForStoppedState(true);
            }
        }
    };

    private final OnClickListener mNextListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();
            mController.skipToNextItem();
        }
    };

    private final OnClickListener mPrevListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();
            mController.skipToPreviousItem();
        }
    };

    private final OnClickListener mBackListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            View parent = (View) getParent();
            if (parent != null) {
                parent.onKeyDown(KeyEvent.KEYCODE_BACK,
                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
            }
        }
    };

    private final OnClickListener mSubtitleListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            removeCallbacks(mHideMainBars);
            removeCallbacks(mHideProgressBar);

            mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
            mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
            displaySettingsWindow(mSubSettingsAdapter);
        }
    };

    private final OnClickListener mVideoQualityListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            removeCallbacks(mHideMainBars);
            removeCallbacks(mHideProgressBar);

            mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
            mSubSettingsAdapter.setTexts(mVideoQualityList);
            mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
            displaySettingsWindow(mSubSettingsAdapter);
        }
    };

    private final OnClickListener mFullScreenListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();

            if (mOnFullScreenListener == null) {
                return;
            }

            final boolean isEnteringFullScreen = !mIsFullScreen;
            if (isEnteringFullScreen) {
                mFullScreenButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_fullscreen_exit));
            } else {
                mFullScreenButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_fullscreen));
            }
            mIsFullScreen = isEnteringFullScreen;
            mOnFullScreenListener.onFullScreen(MediaControlView.this,
                    mIsFullScreen);
        }
    };

    private final OnClickListener mOverflowShowListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();

            mOverflowIsShowing = true;
            mOverflowShowAnimator.start();
        }
    };

    private final OnClickListener mOverflowHideListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            resetHideCallbacks();

            mOverflowIsShowing = false;
            mOverflowHideAnimator.start();
        }
    };

    private final OnClickListener mSettingsButtonListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            removeCallbacks(mHideMainBars);
            removeCallbacks(mHideProgressBar);

            mSettingsMode = SETTINGS_MODE_MAIN;
            mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
            displaySettingsWindow(mSettingsAdapter);
        }
    };

    private final AdapterView.OnItemClickListener mSettingsItemClickListener =
            new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            switch (mSettingsMode) {
                case SETTINGS_MODE_MAIN:
                    if (position == SETTINGS_MODE_AUDIO_TRACK) {
                        mSubSettingsAdapter.setTexts(mAudioTrackList);
                        mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
                        mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
                    } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
                        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
                        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
                        mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
                    }
                    displaySettingsWindow(mSubSettingsAdapter);
                    break;
                case SETTINGS_MODE_AUDIO_TRACK:
                    if (position != mSelectedAudioTrackIndex) {
                        mSelectedAudioTrackIndex = position;
                        if (mAudioTrackCount > 0) {
                            mController.selectAudioTrack(position);
                        }
                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
                                mSubSettingsAdapter.getMainText(position));
                    }
                    dismissSettingsWindow();
                    break;
                case SETTINGS_MODE_PLAYBACK_SPEED:
                    if (position != mSelectedSpeedIndex) {
                        float speed = mPlaybackSpeedMultBy100List.get(position) / 100.0f;
                        mController.setSpeed(speed);
                    }
                    dismissSettingsWindow();
                    break;
                case SETTINGS_MODE_SUBTITLE_TRACK:
                    if (position != mSelectedSubtitleTrackIndex) {
                        if (position > 0) {
                            mController.showSubtitle(position - 1);
                            mSubtitleButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_subtitle_on));
                            mSubtitleButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_cc_is_on));
                        } else {
                            mController.hideSubtitle();
                            mSubtitleButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_subtitle_off));
                            mSubtitleButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_cc_is_off));
                        }
                    }
                    dismissSettingsWindow();
                    break;
                case SETTINGS_MODE_VIDEO_QUALITY:
                    mSelectedVideoQualityIndex = position;
                    dismissSettingsWindow();
                    break;
            }
        }
    };

    private PopupWindow.OnDismissListener mSettingsDismissListener =
            new PopupWindow.OnDismissListener() {
                @Override
                public void onDismiss() {
                    if (mNeedToHideBars) {
                        postDelayed(mHideMainBars, mShowControllerIntervalMs);
                    }
                }
            };

    void updateMetadata() {
        if (!mController.hasMetadata()) {
            return;
        }

        long duration = mController.getDurationMs();
        if (duration != 0) {
            mDuration = duration;
            mTimeView.setVisibility(View.VISIBLE);
            setProgress();
        }

        if (mMediaType != MEDIA_TYPE_MUSIC) {
            CharSequence title = mController.getTitle();
            if (title != null) {
                mTitleView.setText(title.toString());
            }
        } else {
            CharSequence title = mController.getTitle();
            if (title == null) {
                title = mResources.getString(R.string.mcv2_music_title_unknown_text);
            }
            CharSequence artist = mController.getArtistText();
            if (artist == null) {
                artist = mResources.getString(R.string.mcv2_music_artist_unknown_text);
            }
            // Update title for Embedded size type
            mTitleView.setText(title.toString() + " - " + artist.toString());

            // Remove unnecessary buttons
            mVideoQualityButton.setVisibility(View.GONE);
            if (mFfwdButton != null) {
                mFfwdButton.setVisibility(View.GONE);
            }
            if (mRewButton != null) {
                mRewButton.setVisibility(View.GONE);
            }

            Point screenSize = new Point();
            mWindowManager.getDefaultDisplay().getSize(screenSize);
            updateLayout(BOTTOM_BAR_RIGHT_VIEW_MAX_ICON_NUM_MUSIC, getMeasuredWidth(),
                    getMeasuredHeight(), screenSize.x, screenSize.y);
        }
    }

    void updateLayoutForAd() {
        if (mIsAdvertisement) {
            mRewButton.setVisibility(View.GONE);
            mFfwdButton.setVisibility(View.GONE);
            mPrevButton.setVisibility(View.GONE);
            mTimeView.setVisibility(View.GONE);

            mAdSkipView.setVisibility(View.VISIBLE);
            mAdRemainingView.setVisibility(View.VISIBLE);
            mAdExternalLink.setVisibility(View.VISIBLE);

            mProgress.setEnabled(false);
            mNextButton.setEnabled(false);
            mNextButton.setColorFilter(R.color.gray);
        } else {
            mRewButton.setVisibility(View.VISIBLE);
            mFfwdButton.setVisibility(View.VISIBLE);
            mPrevButton.setVisibility(View.VISIBLE);
            mTimeView.setVisibility(View.VISIBLE);

            mAdSkipView.setVisibility(View.GONE);
            mAdRemainingView.setVisibility(View.GONE);
            mAdExternalLink.setVisibility(View.GONE);

            mProgress.setEnabled(true);
            mNextButton.setEnabled(true);
            mNextButton.clearColorFilter();
            disableUnsupportedButtons();
        }
    }

    private void updateLayout(int maxIconNum, int currWidth, int currHeight, int screenWidth,
            int screenHeight) {
        if (mMaxTimeViewWidth == 0) {
            // Save the width of the initial time view since it represents the maximum width that
            // this class supports (00:00:00 · 00:00:00).
            mMaxTimeViewWidth = mTimeView.getWidth();
        }
        int bottomBarRightWidthMax = mIconSize * maxIconNum;
        int fullWidth = mTransportControls.getWidth() + mMaxTimeViewWidth + bottomBarRightWidthMax;
        int screenMaxLength = Math.max(screenWidth, screenHeight);
        int embeddedWidth = mMaxTimeViewWidth + bottomBarRightWidthMax;

        // If Media type is default, the size of MCV2 is full only when the current width is equal
        // to the max length of the screen (only landscape mode). If Media type is music, however,
        // the size of MCV2 is full when the current width is equal to the current screen width
        // (both landscape and portrait modes).
        boolean isFullSize = (mMediaType == MEDIA_TYPE_DEFAULT) ? currWidth == screenMaxLength
                : currWidth == screenWidth;
        if (isFullSize) {
            if (mSizeType != SIZE_TYPE_FULL) {
                updateLayoutForSizeChange(SIZE_TYPE_FULL);
                if (mMediaType == MEDIA_TYPE_MUSIC) {
                    mTitleView.setVisibility(View.GONE);
                } else {
                    mUxState = UX_STATE_NONE_VISIBLE;
                    toggleMediaControlViewVisibility();
                }
            }
        } else if (embeddedWidth <= currWidth) {
            if (mSizeType != SIZE_TYPE_EMBEDDED) {
                updateLayoutForSizeChange(SIZE_TYPE_EMBEDDED);
                if (mMediaType == MEDIA_TYPE_MUSIC) {
                    mTitleView.setVisibility(View.VISIBLE);
                }
            }
        } else {
            if (mSizeType != SIZE_TYPE_MINIMAL) {
                updateLayoutForSizeChange(SIZE_TYPE_MINIMAL);
                if (mMediaType == MEDIA_TYPE_MUSIC) {
                    mTitleView.setVisibility(View.GONE);
                }
            }
        }
    }

    @SuppressWarnings("deprecation")
    private void updateLayoutForSizeChange(int sizeType) {
        mSizeType = sizeType;
        RelativeLayout.LayoutParams timeViewParams =
                (RelativeLayout.LayoutParams) mTimeView.getLayoutParams();
        SeekBar seeker = (SeekBar) mProgress;
        switch (mSizeType) {
            case SIZE_TYPE_EMBEDDED:
                // Relating to Title Bar
                mTitleBar.setVisibility(View.VISIBLE);
                mBackButton.setVisibility(View.GONE);
                mTitleView.setPadding(
                        mResources.getDimensionPixelSize(R.dimen.mcv2_embedded_icon_padding),
                        mTitleView.getPaddingTop(),
                        mTitleView.getPaddingRight(),
                        mTitleView.getPaddingBottom());

                // Relating to Full Screen Button
                if (mOnFullScreenListener != null) {
                    mMinimalSizeFullScreenView.setVisibility(View.GONE);
                    mFullScreenButton = mBasicControls.findViewById(R.id.fullscreen);
                    mFullScreenButton.setOnClickListener(mFullScreenListener);
                }

                // Relating to Center View
                mCenterView.removeAllViews();
                mBottomBarLeftView.removeView(mTransportControls);
                mBottomBarLeftView.setVisibility(View.GONE);
                mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
                mCenterView.addView(mTransportControls);

                // Relating to Progress Bar
                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
                seeker.setThumbOffset(0);
                seeker.invalidate();
                mProgressBuffer.setVisibility(View.VISIBLE);

                // Relating to Bottom Bar
                mBottomBar.setVisibility(View.VISIBLE);
                if (timeViewParams.getRules()[RelativeLayout.LEFT_OF] != 0) {
                    timeViewParams.removeRule(RelativeLayout.LEFT_OF);
                    timeViewParams.addRule(RelativeLayout.RIGHT_OF, R.id.bottom_bar_left);
                }
                break;
            case SIZE_TYPE_FULL:
                // Relating to Title Bar
                mTitleBar.setVisibility(View.VISIBLE);
                mBackButton.setVisibility(View.VISIBLE);
                mTitleView.setPadding(
                        0,
                        mTitleView.getPaddingTop(),
                        mTitleView.getPaddingRight(),
                        mTitleView.getPaddingBottom());

                // Relating to Full Screen Button
                if (mOnFullScreenListener != null) {
                    mMinimalSizeFullScreenView.setVisibility(View.GONE);
                    mFullScreenButton = mBasicControls.findViewById(R.id.fullscreen);
                    mFullScreenButton.setOnClickListener(mFullScreenListener);
                }

                // Relating to Center View
                mCenterView.removeAllViews();
                mBottomBarLeftView.removeView(mTransportControls);
                mTransportControls = inflateTransportControls(R.layout.full_transport_controls);
                mBottomBarLeftView.addView(mTransportControls, 0);
                mBottomBarLeftView.setVisibility(View.VISIBLE);

                // Relating to Progress Bar
                seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
                seeker.setThumbOffset(0);
                seeker.invalidate();
                mProgressBuffer.setVisibility(View.VISIBLE);

                // Relating to Bottom Bar
                mBottomBar.setVisibility(View.VISIBLE);
                if (timeViewParams.getRules()[RelativeLayout.RIGHT_OF] != 0) {
                    timeViewParams.removeRule(RelativeLayout.RIGHT_OF);
                    timeViewParams.addRule(RelativeLayout.LEFT_OF, R.id.basic_controls);
                }
                break;
            case SIZE_TYPE_MINIMAL:
                // Relating to Title Bar
                mTitleBar.setVisibility(View.GONE);
                mBackButton.setVisibility(View.GONE);

                // Relating to Full Screen Button
                if (mOnFullScreenListener != null) {
                    mMinimalSizeFullScreenView.setVisibility(View.VISIBLE);
                    mFullScreenButton = mMinimalSizeFullScreenView.findViewById(
                            R.id.minimal_fullscreen);
                    mFullScreenButton.setOnClickListener(mFullScreenListener);
                }

                // Relating to Center View
                mCenterView.removeAllViews();
                mBottomBarLeftView.removeView(mTransportControls);
                mTransportControls = inflateTransportControls(R.layout.minimal_transport_controls);
                mCenterView.addView(mTransportControls);

                // Relating to Progress Bar
                seeker.setThumb(null);
                mProgressBuffer.setVisibility(View.GONE);

                // Relating to Bottom Bar
                mBottomBar.setVisibility(View.GONE);
                break;
        }
        mTimeView.setLayoutParams(timeViewParams);

        // Update play/pause and ffwd buttons based on whether the media is currently stopped or
        // not.
        updateForStoppedState(mIsStopped);

        if (mIsFullScreen) {
            mFullScreenButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_fullscreen_exit));
        } else {
            mFullScreenButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_fullscreen));
        }
    }

    private View inflateTransportControls(int layoutId) {
        View v = inflateLayout(getContext(), layoutId);
        mPlayPauseButton = v.findViewById(R.id.pause);
        if (mPlayPauseButton != null) {
            mPlayPauseButton.requestFocus();
            mPlayPauseButton.setOnClickListener(mPlayPauseListener);
        }
        mFfwdButton = v.findViewById(R.id.ffwd);
        if (mFfwdButton != null) {
            if (mMediaType == MEDIA_TYPE_MUSIC) {
                mFfwdButton.setVisibility(View.GONE);
            } else {
                mFfwdButton.setOnClickListener(mFfwdListener);
            }
        }
        mRewButton = v.findViewById(R.id.rew);
        if (mRewButton != null) {
            if (mMediaType == MEDIA_TYPE_MUSIC) {
                mRewButton.setVisibility(View.GONE);
            } else {
                mRewButton.setOnClickListener(mRewListener);
            }
        }
        mNextButton = v.findViewById(R.id.next);
        if (mNextButton != null) {
            if (mController.canSkipToNext()) {
                mNextButton.setOnClickListener(mNextListener);
            } else {
                mNextButton.setVisibility(View.GONE);
            }
        }
        mPrevButton = v.findViewById(R.id.prev);
        if (mPrevButton != null) {
            if (mController.canSkipToPrevious()) {
                mPrevButton.setOnClickListener(mPrevListener);
            } else {
                mPrevButton.setVisibility(View.GONE);
            }
        }
        return v;
    }

    private void initializeSettingsLists() {
        mSettingsMainTextsList = new ArrayList<String>();
        mSettingsMainTextsList.add(
                mResources.getString(R.string.MediaControlView_audio_track_text));
        mSettingsMainTextsList.add(
                mResources.getString(R.string.MediaControlView_playback_speed_text));

        mSettingsSubTextsList = new ArrayList<String>();
        mSettingsSubTextsList.add(
                mResources.getString(R.string.MediaControlView_audio_track_none_text));
        String normalSpeed = mResources.getString(R.string.MediaControlView_playback_speed_normal);
        mSettingsSubTextsList.add(normalSpeed);
        mSettingsSubTextsList.add(RESOURCE_EMPTY);

        mSettingsIconIdsList = new ArrayList<Integer>();
        mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
        mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);

        mAudioTrackList = new ArrayList<String>();
        mAudioTrackList.add(
                mResources.getString(R.string.MediaControlView_audio_track_none_text));

        mVideoQualityList = new ArrayList<String>();
        mVideoQualityList.add(
                mResources.getString(R.string.MediaControlView_video_quality_auto_text));

        mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
                mResources.getStringArray(R.array.MediaControlView_playback_speeds)));
        // Select the normal speed (1x) as the default value.
        mPlaybackSpeedTextList.add(PLAYBACK_SPEED_1x_INDEX, normalSpeed);
        mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;

        mPlaybackSpeedMultBy100List = new ArrayList<Integer>();
        int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
        for (int i = 0; i < speeds.length; i++) {
            mPlaybackSpeedMultBy100List.add(speeds[i]);
        }
        mCustomPlaybackSpeedIndex = -1;
    }

    /**
     * @return true iff the current media item is from network.
     */
    @VisibleForTesting
    boolean isCurrentMediaItemFromNetwork() {
        MediaItem currentMediaItem = mController.getCurrentMediaItem();

        if (!(currentMediaItem instanceof UriMediaItem)) {
            return false;
        }

        Uri uri = ((UriMediaItem) currentMediaItem).getUri();
        return UriUtil.isFromNetwork(uri);
    }

    void displaySettingsWindow(BaseAdapter adapter) {
        // Set Adapter
        mSettingsListView.setAdapter(adapter);

        // Set width of window
        int itemWidth = (mSizeType == SIZE_TYPE_EMBEDDED)
                ? mEmbeddedSettingsItemWidth : mFullSettingsItemWidth;
        mSettingsWindow.setWidth(itemWidth);

        // Calculate height of window
        int maxHeight = getMeasuredHeight() + mSettingsWindowMargin * 2;
        int totalHeight = adapter.getCount() * mSettingsItemHeight;
        int height = (totalHeight < maxHeight) ? totalHeight : maxHeight;
        mSettingsWindow.setHeight(height);

        // Show window
        mNeedToHideBars = false;
        mSettingsWindow.dismiss();
        mSettingsWindow.showAsDropDown(this, mSettingsWindowMargin,
                mSettingsWindowMargin - height, Gravity.BOTTOM | Gravity.RIGHT);
        mNeedToHideBars = true;
    }

    void dismissSettingsWindow() {
        mNeedToHideBars = true;
        mSettingsWindow.dismiss();
    }

    void animateOverflow(ValueAnimator animation) {
        RelativeLayout.LayoutParams extraControlsParams =
                (RelativeLayout.LayoutParams) mExtraControls.getLayoutParams();
        int iconWidth = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_size);
        // Currently, mExtraControls view is set to the right end of the bottom bar
        // view. This animates the view by setting the initial margin value to the
        // negative value of its width ((-2) * iconWidth) and the final margin value
        // to the positive value of the overflow button width (iconWidth).
        int extraControlMargin = (-2 * iconWidth)
                + (int) (3 * iconWidth * (float) animation.getAnimatedValue());
        extraControlsParams.setMargins(0, 0, extraControlMargin, 0);
        mExtraControls.setLayoutParams(extraControlsParams);

        mTimeView.setAlpha(1 - (float) animation.getAnimatedValue());
        mBasicControls.setAlpha(1 - (float) animation.getAnimatedValue());

        if (mSizeType == SIZE_TYPE_FULL && mMediaType == MEDIA_TYPE_DEFAULT) {
            int transportControlMargin =
                    (-1) * (int) (iconWidth * (float) animation.getAnimatedValue());
            LinearLayout.LayoutParams transportControlsParams =
                    (LinearLayout.LayoutParams) mTransportControls.getLayoutParams();
            transportControlsParams.setMargins(transportControlMargin, 0, 0, 0);
            mTransportControls.setLayoutParams(transportControlsParams);

            mFfwdButton.setAlpha(1 - (float) animation.getAnimatedValue());
        }
    }

    void resetHideCallbacks() {
        removeCallbacks(mHideMainBars);
        removeCallbacks(mHideProgressBar);
        postDelayed(mHideMainBars, mShowControllerIntervalMs);
    }

    void updateAllowedCommands(SessionCommandGroup commands) {
        if (DEBUG) {
            Log.d(TAG, "updateAllowedCommands(): commands: " + commands);
        }

        if (mController.getAllowedCommands() == commands) {
            return;
        }
        mController.setAllowedCommands(commands);

        if (commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_PAUSE)) {
            mPlayPauseButton.setVisibility(View.VISIBLE);
            mPlayPauseButton.setEnabled(true);
        } else {
            mPlayPauseButton.setVisibility(View.GONE);
        }
        if (commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_REWIND)
                && mMediaType != MEDIA_TYPE_MUSIC) {
            if (mRewButton != null) {
                mRewButton.setVisibility(View.VISIBLE);
                mRewButton.setEnabled(true);
            }
        } else {
            if (mRewButton != null) {
                mRewButton.setVisibility(View.GONE);
            }
        }
        if (commands.hasCommand(SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD)
                && mMediaType != MEDIA_TYPE_MUSIC) {
            if (mFfwdButton != null) {
                mFfwdButton.setVisibility(View.VISIBLE);
                mFfwdButton.setEnabled(true);
            }
        } else {
            if (mFfwdButton != null) {
                mFfwdButton.setVisibility(View.GONE);
            }
        }
        if (commands.hasCommand(
                SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM)) {
            if (mPrevButton != null) {
                mPrevButton.setVisibility(VISIBLE);
                mPrevButton.setEnabled(true);
            }
        } else {
            if (mPrevButton != null) {
                mPrevButton.setVisibility(View.GONE);
            }
        }
        if (commands.hasCommand(
                SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM)) {
            if (mNextButton != null) {
                mNextButton.setVisibility(VISIBLE);
                mNextButton.setEnabled(true);
            }
        } else {
            if (mNextButton != null) {
                mNextButton.setVisibility(View.GONE);
            }
        }
        if (commands.hasCommand(SessionCommand.COMMAND_CODE_PLAYER_SEEK_TO)) {
            mSeekAvailable = true;
            mProgress.setEnabled(true);
        }
        if (commands.hasCommand(new SessionCommand(COMMAND_SHOW_SUBTITLE, null))
                && commands.hasCommand(new SessionCommand(COMMAND_HIDE_SUBTITLE, null))) {
            mSubtitleButton.setVisibility(View.VISIBLE);
        } else {
            mSubtitleButton.setVisibility(View.GONE);
        }
    }

    private int retrieveOrientation() {
        DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();
        int width = dm.widthPixels;
        int height = dm.heightPixels;

        return (height > width)
                ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
                : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    }

    boolean shouldNotHideBars() {
        return (mMediaType == MEDIA_TYPE_MUSIC && mSizeType == SIZE_TYPE_FULL)
                || mAccessibilityManager.isTouchExplorationEnabled()
                || mController.getPlaybackState() == SessionPlayer.PLAYER_STATE_ERROR
                || mController.getPlaybackState() == SessionPlayer.PLAYER_STATE_IDLE;
    }

    void seekTo(long newPosition, boolean shouldSeekNow) {
        int positionOnProgressBar = (mDuration <= 0)
                ? 0 : (int) (MAX_PROGRESS * newPosition / mDuration);
        mProgress.setProgress(positionOnProgressBar);
        mCurrentTime.setText(stringForTime(newPosition));

        if (mCurrentSeekPosition == SEEK_POSITION_NOT_SET) {
            // If current seek position is not set, update its value and seek now if necessary.
            mCurrentSeekPosition = newPosition;

            if (shouldSeekNow) {
                mController.seekTo(mCurrentSeekPosition);
            }
        } else {
            // If current seek position is already set, update the next seek position.
            mNextSeekPosition = newPosition;
        }
    }

    private void showAllBars() {
        if (mUxState != UX_STATE_ALL_VISIBLE) {
            removeCallbacks(mHideMainBars);
            removeCallbacks(mHideProgressBar);
            // b/112570875
            post(mShowMainBars);
        } else {
            resetHideCallbacks();
        }
    }

    private void hideSettingsAndOverflow() {
        mSettingsWindow.dismiss();
        if (mOverflowIsShowing) {
            mOverflowHideAnimator.start();
        }
    }

    long getLatestSeekPosition() {
        if (mNextSeekPosition != SEEK_POSITION_NOT_SET) {
            return mNextSeekPosition;
        } else if (mCurrentSeekPosition != SEEK_POSITION_NOT_SET) {
            return mCurrentSeekPosition;
        }
        return mController.getCurrentPosition();
    }

    void removeCustomSpeedFromList() {
        mPlaybackSpeedMultBy100List.remove(mCustomPlaybackSpeedIndex);
        mPlaybackSpeedTextList.remove(mCustomPlaybackSpeedIndex);
        mCustomPlaybackSpeedIndex = -1;
    }

    void updateSelectedSpeed(int selectedSpeedIndex, String selectedSpeedText) {
        mSelectedSpeedIndex = selectedSpeedIndex;
        mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED, selectedSpeedText);
        mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
        mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
    }

    void updateForStoppedState(boolean isStopped) {
        if (isStopped) {
            mIsStopped = true;
            if (mPlayPauseButton != null) {
                mPlayPauseButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_replay_circle_filled));
                mPlayPauseButton.setContentDescription(
                        mResources.getString(R.string.mcv2_replay_button_desc));
            }
            if (mFfwdButton != null) {
                mFfwdButton.setAlpha(0.5f);
                mFfwdButton.setEnabled(false);
            }
        } else {
            mIsStopped = false;
            if (mPlayPauseButton != null) {
                if (mController.isPlaying()) {
                    mPlayPauseButton.setImageDrawable(
                            mResources.getDrawable(R.drawable.ic_pause_circle_filled));
                    mPlayPauseButton.setContentDescription(
                            mResources.getString(R.string.mcv2_pause_button_desc));
                } else {
                    mPlayPauseButton.setImageDrawable(
                            mResources.getDrawable(R.drawable.ic_play_circle_filled));
                    mPlayPauseButton.setContentDescription(
                            mResources.getString(R.string.mcv2_play_button_desc));
                }
            }
            if (mFfwdButton != null) {
                mFfwdButton.setAlpha(1.0f);
                mFfwdButton.setEnabled(true);
            }
        }
    }

    private class SettingsAdapter extends BaseAdapter {
        private List<Integer> mIconIds;
        private List<String> mMainTexts;
        private List<String> mSubTexts;

        SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
                @Nullable List<Integer> iconIds) {
            mMainTexts = mainTexts;
            mSubTexts = subTexts;
            mIconIds = iconIds;
        }

        public void updateSubTexts(List<String> subTexts) {
            mSubTexts = subTexts;
            notifyDataSetChanged();
        }

        public String getMainText(int position) {
            if (mMainTexts != null) {
                if (position < mMainTexts.size()) {
                    return mMainTexts.get(position);
                }
            }
            return RESOURCE_EMPTY;
        }

        @Override
        public int getCount() {
            return (mMainTexts == null) ? 0 : mMainTexts.size();
        }

        @Override
        public long getItemId(int position) {
            // Auto-generated method stub--does not have any purpose here
            return 0;
        }

        @Override
        public Object getItem(int position) {
            // Auto-generated method stub--does not have any purpose here
            return null;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            View row;
            if (mSizeType == SIZE_TYPE_FULL) {
                row = inflateLayout(getContext(), R.layout.full_settings_list_item);
            } else {
                row = inflateLayout(getContext(), R.layout.embedded_settings_list_item);
            }
            TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
            TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
            ImageView iconView = (ImageView) row.findViewById(R.id.icon);

            // Set main text
            mainTextView.setText(mMainTexts.get(position));

            // Remove sub text and center the main text if sub texts do not exist at all or the sub
            // text at this particular position is empty.
            if (mSubTexts == null || RESOURCE_EMPTY.equals(mSubTexts.get(position))) {
                subTextView.setVisibility(View.GONE);
            } else {
                // Otherwise, set sub text.
                subTextView.setText(mSubTexts.get(position));
            }

            // Remove main icon and set visibility to gone if icons are set to null or the icon at
            // this particular position is set to RESOURCE_NON_EXISTENT.
            if (mIconIds == null || mIconIds.get(position) == RESOURCE_NON_EXISTENT) {
                iconView.setVisibility(View.GONE);
            } else {
                // Otherwise, set main icon.
                iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position)));
            }
            return row;
        }

        public void setSubTexts(List<String> subTexts) {
            mSubTexts = subTexts;
        }
    }

    private class SubSettingsAdapter extends BaseAdapter {
        private List<String> mTexts;
        private int mCheckPosition;

        SubSettingsAdapter(List<String> texts, int checkPosition) {
            mTexts = texts;
            mCheckPosition = checkPosition;
        }

        public String getMainText(int position) {
            if (mTexts != null) {
                if (position < mTexts.size()) {
                    return mTexts.get(position);
                }
            }
            return RESOURCE_EMPTY;
        }

        @Override
        public int getCount() {
            return (mTexts == null) ? 0 : mTexts.size();
        }

        @Override
        public long getItemId(int position) {
            // Auto-generated method stub--does not have any purpose here
            return 0;
        }

        @Override
        public Object getItem(int position) {
            // Auto-generated method stub--does not have any purpose here
            return null;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            View row;
            if (mSizeType == SIZE_TYPE_FULL) {
                row = inflateLayout(getContext(), R.layout.full_sub_settings_list_item);
            } else {
                row = inflateLayout(getContext(), R.layout.embedded_sub_settings_list_item);
            }
            TextView textView = (TextView) row.findViewById(R.id.text);
            ImageView checkView = (ImageView) row.findViewById(R.id.check);

            textView.setText(mTexts.get(position));
            if (position != mCheckPosition) {
                checkView.setVisibility(View.INVISIBLE);
            }
            return row;
        }

        public void setTexts(List<String> texts) {
            mTexts = texts;
        }

        public void setCheckPosition(int checkPosition) {
            mCheckPosition = checkPosition;
        }
    }

    class Controller {
        private MediaController mController;
        int mPlaybackState = SessionPlayer.PLAYER_STATE_IDLE;
        int mPrevState = SessionPlayer.PLAYER_STATE_IDLE;
        MediaMetadata mMediaMetadata;
        private Executor mCallbackExecutor;
        SessionCommandGroup mAllowedCommands;

        Controller() {
            mCallbackExecutor = ContextCompat.getMainExecutor(getContext());
        }

        void setSessionToken(SessionToken token) {
            if (mController != null) {
                mController.close();
            }
            mController = new MediaController(getContext(), token, mCallbackExecutor,
                    new MediaControllerCallback());
            mPlaybackState = mController.getPlayerState();
            MediaItem currentItem = mController.getCurrentMediaItem();
            mMediaMetadata = currentItem != null ? currentItem.getMetadata() : null;
        }

        boolean hasMetadata() {
            return mMediaMetadata != null;
        }

        boolean isPlaying() {
            return mPlaybackState == SessionPlayer.PLAYER_STATE_PLAYING;
        }

        long getCurrentPosition() {
            if (mController != null) {
                long currentPosition = mController.getCurrentPosition();
                return (currentPosition < 0) ? 0 : currentPosition;
            }
            return 0;
        }

        long getBufferPercentage() {
            if (mController != null && mDuration != 0) {
                long bufferedPos = mController.getBufferedPosition();
                return (bufferedPos < 0) ? -1 : (bufferedPos * 100 / mDuration);
            }
            return 0;
        }

        int getPlaybackState() {
            if (mController != null) {
                return mController.getPlayerState();
            }
            return SessionPlayer.PLAYER_STATE_IDLE;
        }

        boolean canPause() {
            return mAllowedCommands != null && mAllowedCommands.hasCommand(
                    SessionCommand.COMMAND_CODE_PLAYER_PAUSE);
        }

        boolean canSeekBackward() {
            return mAllowedCommands != null && mAllowedCommands.hasCommand(
                    SessionCommand.COMMAND_CODE_SESSION_REWIND);
        }

        boolean canSeekForward() {
            return mAllowedCommands != null && mAllowedCommands.hasCommand(
                    SessionCommand.COMMAND_CODE_SESSION_FAST_FORWARD);
        }

        boolean canSkipToNext() {
            return mAllowedCommands != null && mAllowedCommands.hasCommand(
                    SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_NEXT_PLAYLIST_ITEM);
        }

        boolean canSkipToPrevious() {
            return mAllowedCommands != null && mAllowedCommands.hasCommand(
                    SessionCommand.COMMAND_CODE_PLAYER_SKIP_TO_PREVIOUS_PLAYLIST_ITEM);
        }

        void pause() {
            if (mController != null) {
                mController.pause();
            }
        }

        void play() {
            if (mController != null) {
                mController.play();
            }
        }

        void seekTo(long posMs) {
            if (mController != null) {
                mController.seekTo(posMs);
            }
        }

        void skipToNextItem() {
            if (mController != null) {
                mController.skipToNextPlaylistItem();
            }
        }

        void skipToPreviousItem() {
            if (mController != null) {
                mController.skipToPreviousPlaylistItem();
            }
        }

        void setSpeed(float speed) {
            if (mController != null) {
                mController.setPlaybackSpeed(speed);
            }
        }

        void selectAudioTrack(int trackIndex) {
            if (mController != null) {
                Bundle extra = new Bundle();
                extra.putInt(KEY_SELECTED_AUDIO_INDEX, trackIndex);
                mController.sendCustomCommand(
                        new SessionCommand(COMMAND_SELECT_AUDIO_TRACK, null),
                        extra);
            }
        }

        void showSubtitle(int trackIndex) {
            if (mController != null) {
                Bundle extra = new Bundle();
                extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, trackIndex);
                mController.sendCustomCommand(
                        new SessionCommand(COMMAND_SHOW_SUBTITLE, null), extra);
            }
        }

        void hideSubtitle() {
            if (mController != null) {
                mController.sendCustomCommand(
                        new SessionCommand(COMMAND_HIDE_SUBTITLE, null), null);
            }
        }

        long getDurationMs() {
            // TODO Remove this if-block after b/109639439 is fixed.
            if (mMediaMetadata != null) {
                if (mMediaMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                    return mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
                }
            }
            if (mController != null) {
                return mController.getDuration();
            }
            return 0;
        }

        CharSequence getTitle() {
            if (mMediaMetadata != null) {
                if (mMediaMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
                    return mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE);
                }
            }
            return null;
        }

        CharSequence getArtistText() {
            if (mMediaMetadata != null) {
                if (mMediaMetadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
                    return mMediaMetadata.getText(MediaMetadata.METADATA_KEY_ARTIST);
                }
            }
            return null;
        }

        MediaItem getCurrentMediaItem() {
            if (mController != null) {
                return mController.getCurrentMediaItem();
            }
            return null;
        }

        void setAllowedCommands(SessionCommandGroup commands) {
            mAllowedCommands = commands;
        }

        SessionCommandGroup getAllowedCommands() {
            return mAllowedCommands;
        }

        class MediaControllerCallback extends MediaController.ControllerCallback {
            @Override
            public void onPlayerStateChanged(@NonNull MediaController controller,
                    @SessionPlayer.PlayerState int state) {
                if (DEBUG) {
                    Log.d(TAG, "onPlayerStateChanged(state: " + state + ")");
                }
                mPlaybackState = state;

                // Update pause button depending on playback state for the following two reasons:
                //   1) Need to handle case where app customizes playback state behavior when app
                //      activity is resumed.
                //   2) Need to handle case where the media file reaches end of duration.
                if (mPlaybackState != mPrevState) {
                    switch (mPlaybackState) {
                        case SessionPlayer.PLAYER_STATE_PLAYING:
                            removeCallbacks(mUpdateProgress);
                            post(mUpdateProgress);
                            resetHideCallbacks();
                            updateForStoppedState(false);
                            break;
                        case SessionPlayer.PLAYER_STATE_PAUSED:
                            mPlayPauseButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_play_circle_filled));
                            mPlayPauseButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_play_button_desc));
                            removeCallbacks(mUpdateProgress);
                            break;
                        case SessionPlayer.PLAYER_STATE_ERROR:
                            MediaControlView.this.setEnabled(false);
                            mPlayPauseButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_play_circle_filled));
                            mPlayPauseButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_play_button_desc));
                            removeCallbacks(mUpdateProgress);
                            if (getWindowToken() != null) {
                                new AlertDialog.Builder(getContext())
                                        .setMessage(R.string.mcv2_playback_error_text)
                                        .setPositiveButton(R.string.mcv2_error_dialog_button,
                                                new DialogInterface.OnClickListener() {
                                                    @Override
                                                    public void onClick(
                                                            DialogInterface dialogInterface,
                                                            int i) {
                                                        dialogInterface.dismiss();
                                                    }
                                                })
                                        .setCancelable(true)
                                        .show();
                            }
                    }
                    mPrevState = mPlaybackState;
                }
            }

            @Override
            public void onSeekCompleted(MediaController controller, long position) {
                if (DEBUG) {
                    Log.d(TAG, "onSeekCompleted(): " + position);
                }
                // Update progress bar and time text.
                int positionOnProgressBar = (mDuration <= 0)
                        ? 0 : (int) (MAX_PROGRESS * position / mDuration);
                mProgress.setProgress(positionOnProgressBar);
                mCurrentTime.setText(stringForTime(position));

                if (mNextSeekPosition != SEEK_POSITION_NOT_SET) {
                    mCurrentSeekPosition = mNextSeekPosition;

                    // If the next seek position is set, seek to that position.
                    MediaControlView.this.mController.seekTo(mNextSeekPosition);
                    mNextSeekPosition = SEEK_POSITION_NOT_SET;
                } else {
                    mCurrentSeekPosition = SEEK_POSITION_NOT_SET;

                    // If the next seek position is not set, start to update progress.
                    removeCallbacks(mUpdateProgress);
                    removeCallbacks(mHideMainBars);
                    post(mUpdateProgress);
                    postDelayed(mHideMainBars, mShowControllerIntervalMs);
                }
            }

            @Override
            public void onCurrentMediaItemChanged(@NonNull MediaController controller,
                    @Nullable MediaItem mediaItem) {
                if (DEBUG) {
                    Log.d(TAG, "onCurrentMediaItemChanged(): " + mediaItem);
                }
                if (mediaItem != null) {
                    mMediaMetadata = mediaItem.getMetadata();
                    updateMetadata();
                }
            }

            @Override
            public void onPlaybackCompleted(MediaController controller) {
                if (DEBUG) {
                    Log.d(TAG, "onPlaybackCompleted()");
                }
                updateForStoppedState(true);
                // The progress bar and current time text may not have been updated.
                mProgress.setProgress(MAX_PROGRESS);
                mCurrentTime.setText(stringForTime(mDuration));
            }

            @Override
            public void onConnected(@NonNull MediaController controller,
                    @NonNull SessionCommandGroup allowedCommands) {
                if (DEBUG) {
                    Log.d(TAG, "onConnected(): " + allowedCommands);
                }
                updateAllowedCommands(allowedCommands);

                MediaItem mediaItem = controller.getCurrentMediaItem();
                if (mediaItem != null) {
                    mMediaMetadata = mediaItem.getMetadata();
                    updateMetadata();
                }
            }

            @Override
            public void onAllowedCommandsChanged(@NonNull MediaController controller,
                    @NonNull SessionCommandGroup commands) {
                updateAllowedCommands(commands);
            }

            @Override
            public void onPlaylistChanged(@NonNull MediaController controller,
                    @NonNull List<MediaItem> list,
                    @Nullable MediaMetadata metadata) {
                if (DEBUG) {
                    Log.d(TAG, "onPlaylistChanged(): list: " + list);
                }
            }

            @Override
            public void onPlaybackSpeedChanged(@NonNull MediaController controller, float speed) {
                int customSpeedMultBy100 = Math.round(speed * 100);
                // An application may set a custom playback speed that is not included in the
                // default playback speed list. The code below handles adding/removing the custom
                // playback speed to the default list.
                if (mCustomPlaybackSpeedIndex != -1) {
                    // Remove existing custom playback speed
                    removeCustomSpeedFromList();
                }

                if (mPlaybackSpeedMultBy100List.contains(customSpeedMultBy100)) {
                    for (int i = 0; i < mPlaybackSpeedMultBy100List.size(); i++) {
                        if (customSpeedMultBy100 == mPlaybackSpeedMultBy100List.get(i)) {
                            updateSelectedSpeed(i, mPlaybackSpeedTextList.get(i));
                            break;
                        }
                    }
                } else {
                    String customSpeedText = mResources.getString(
                            R.string.MediaControlView_custom_playback_speed_text,
                            customSpeedMultBy100 / 100.0f);

                    for (int i = 0; i < mPlaybackSpeedMultBy100List.size(); i++) {
                        if (customSpeedMultBy100 < mPlaybackSpeedMultBy100List.get(i)) {
                            mPlaybackSpeedMultBy100List.add(i, customSpeedMultBy100);
                            mPlaybackSpeedTextList.add(i, customSpeedText);
                            updateSelectedSpeed(i, customSpeedText);
                            break;
                        }
                        // Add to end of list if the custom speed value is greater than all the
                        // value in the default speed list.
                        if (i == mPlaybackSpeedMultBy100List.size() - 1
                                && customSpeedMultBy100 > mPlaybackSpeedMultBy100List.get(i)) {
                            mPlaybackSpeedMultBy100List.add(customSpeedMultBy100);
                            mPlaybackSpeedTextList.add(customSpeedText);
                            updateSelectedSpeed(i + 1, customSpeedText);
                        }
                    }
                    mCustomPlaybackSpeedIndex = mSelectedSpeedIndex;
                }
            }

            @Override
            public MediaController.ControllerResult onCustomCommand(
                    @NonNull MediaController controller, @NonNull SessionCommand command,
                    @Nullable Bundle args) {
                if (DEBUG) {
                    Log.d(TAG, "onCustomCommand(): command: " + command);
                }
                switch (command.getCustomCommand()) {
                    case EVENT_UPDATE_TRACK_STATUS:
                        mVideoTrackCount = (args != null) ? args.getInt(KEY_VIDEO_TRACK_COUNT) : 0;
                        // If there is one or more audio tracks, and this information has not been
                        // reflected into the Settings window yet, automatically check the first
                        // track.
                        // Otherwise, the Audio Track selection will be defaulted to "None".
                        mAudioTrackCount = (args != null) ? args.getInt(KEY_AUDIO_TRACK_COUNT) : 0;
                        mAudioTrackList = new ArrayList<String>();
                        if (mAudioTrackCount > 0) {
                            for (int i = 0; i < mAudioTrackCount; i++) {
                                String track = mResources.getString(
                                        R.string.MediaControlView_audio_track_number_text, i + 1);
                                mAudioTrackList.add(track);
                            }
                            // Change sub text inside the Settings window.
                            mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
                                    mAudioTrackList.get(0));
                        } else {
                            mAudioTrackList.add(mResources.getString(
                                    R.string.MediaControlView_audio_track_none_text));
                        }
                        if (mVideoTrackCount == 0 && mAudioTrackCount > 0) {
                            mMediaType = MEDIA_TYPE_MUSIC;
                        }
                        mSubtitleTrackCount = (args != null)
                                ? args.getInt(KEY_SUBTITLE_TRACK_COUNT) : 0;
                        List<String> subtitleTracksLanguageList = (args != null)
                                ? args.getStringArrayList(KEY_SUBTITLE_TRACK_LANGUAGE_LIST) : null;
                        mSubtitleDescriptionsList = new ArrayList<String>();
                        if (mSubtitleTrackCount > 0) {
                            mSubtitleButton.setAlpha(1.0f);
                            mSubtitleButton.setEnabled(true);
                            mSubtitleDescriptionsList.add(mResources.getString(
                                    R.string.MediaControlView_subtitle_off_text));
                            for (int i = 0; i < mSubtitleTrackCount; i++) {
                                String lang = subtitleTracksLanguageList.get(i);
                                String track;
                                if (lang.equals("")) {
                                    track = mResources.getString(
                                            R.string.MediaControlView_subtitle_track_number_text,
                                            i + 1);
                                } else {
                                    track = mResources.getString(
                                            R.string
                                            .MediaControlView_subtitle_track_number_and_lang_text,
                                            i + 1, lang);
                                }
                                mSubtitleDescriptionsList.add(track);
                            }
                        } else {
                            if (mMediaType == MEDIA_TYPE_MUSIC) {
                                mSubtitleButton.setVisibility(View.GONE);
                            } else {
                                mSubtitleButton.setAlpha(0.5f);
                                mSubtitleButton.setEnabled(false);
                            }
                        }
                        break;
                    case EVENT_UPDATE_MEDIA_TYPE_STATUS:
                        boolean isAd = (args != null)
                                && args.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
                        if (isAd != mIsAdvertisement) {
                            mIsAdvertisement = isAd;
                            updateLayoutForAd();
                        }
                        break;
                    case EVENT_UPDATE_SUBTITLE_SELECTED:
                        int selectedTrackIndex = args != null
                                ? args.getInt(KEY_SELECTED_SUBTITLE_INDEX, -1)
                                : -1;
                        if (selectedTrackIndex < 0 || selectedTrackIndex >= mSubtitleTrackCount) {
                            Log.w(TAG, "Selected subtitle track index (" + selectedTrackIndex
                                    + ") is out of range.");
                            break;
                        }
                        mSelectedSubtitleTrackIndex = selectedTrackIndex + 1;
                        if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
                            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
                        }
                        break;
                    case EVENT_UPDATE_SUBTITLE_DESELECTED:
                        mSelectedSubtitleTrackIndex = 0;
                        if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
                            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
                        }
                        break;
                    default:
                        return new MediaController.ControllerResult(
                                RESULT_CODE_NOT_SUPPORTED, null);
                }
                return new MediaController.ControllerResult(RESULT_CODE_SUCCESS, null);
            }
        }
    }
}