/*
 * Copyright 2017 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 com.android.widget;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.media.SessionToken2;
import android.media.update.MediaControlView2Provider;
import android.media.update.ViewGroupProvider;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.MediaControlView2;
import android.widget.ProgressBar;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

import com.android.media.update.ApiHelper;
import com.android.media.update.R;
import com.android.support.mediarouter.app.MediaRouteButton;
import com.android.support.mediarouter.media.MediaRouter;
import com.android.support.mediarouter.media.MediaRouteSelector;

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

public class MediaControlView2Impl extends BaseLayout implements MediaControlView2Provider {
    private static final String TAG = "MediaControlView2";

    private final MediaControlView2 mInstance;

    static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";

    // TODO: Move these constants to public api to support custom video view.
    // TODO: Combine these constants into one regarding TrackInfo.
    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_PLAYBACK_SPEED = "PlaybackSpeed";
    static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
    static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
    static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";

    // TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
    static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
    static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";

    // 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";
    // TODO: remove once the implementation is revised
    public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
    // String for sending command to select audio track to MediaSession.
    static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
    // String for sending command to set playback speed to MediaSession.
    static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
    // String for sending command to mute audio to MediaSession.
    static final String COMMAND_MUTE= "Mute";
    // String for sending command to unmute audio to MediaSession.
    static final String COMMAND_UNMUTE = "Unmute";

    private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
    private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
    private static final int SETTINGS_MODE_HELP = 2;
    private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
    private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
    private static final int SETTINGS_MODE_MAIN = 5;
    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 SIZE_TYPE_EMBEDDED = 0;
    private static final int SIZE_TYPE_FULL = 1;
    // TODO: add support for Minimal size type.
    private static final int SIZE_TYPE_MINIMAL = 2;

    private static final int MAX_PROGRESS = 1000;
    private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
    private static final int REWIND_TIME_MS = 10000;
    private static final int FORWARD_TIME_MS = 30000;
    private static final int AD_SKIP_WAIT_TIME_MS = 5000;
    private static final int RESOURCE_NON_EXISTENT = -1;
    private static final String RESOURCE_EMPTY = "";

    private Resources mResources;
    private MediaController mController;
    private MediaController.TransportControls mControls;
    private PlaybackState mPlaybackState;
    private MediaMetadata mMetadata;
    private int mDuration;
    private int mPrevState;
    private int mPrevWidth;
    private int mPrevHeight;
    private int mOriginalLeftBarWidth;
    private int mVideoTrackCount;
    private int mAudioTrackCount;
    private int mSubtitleTrackCount;
    private int mSettingsMode;
    private int mSelectedSubtitleTrackIndex;
    private int mSelectedAudioTrackIndex;
    private int mSelectedVideoQualityIndex;
    private int mSelectedSpeedIndex;
    private int mEmbeddedSettingsItemWidth;
    private int mFullSettingsItemWidth;
    private int mEmbeddedSettingsItemHeight;
    private int mFullSettingsItemHeight;
    private int mSettingsWindowMargin;
    private int mMediaType;
    private int mSizeType;
    private long mPlaybackActions;
    private boolean mDragging;
    private boolean mIsFullScreen;
    private boolean mOverflowExpanded;
    private boolean mIsStopped;
    private boolean mSubtitleIsEnabled;
    private boolean mSeekAvailable;
    private boolean mIsAdvertisement;
    private boolean mIsMute;
    private boolean mNeedUXUpdate;

    // 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;
    private View mTransportControls;
    private ImageButton mPlayPauseButton;
    private ImageButton mFfwdButton;
    private ImageButton mRewButton;
    private ImageButton mNextButton;
    private ImageButton mPrevButton;

    // Relating to Minimal Extra View
    private LinearLayout mMinimalExtraView;

    // Relating to Progress Bar View
    private 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;
    private TextView mCurrentTime;
    private TextView mAdSkipView;
    private StringBuilder mFormatBuilder;
    private Formatter mFormatter;

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

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

    public MediaControlView2Impl(MediaControlView2 instance,
            ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
        super(instance, superProvider, privateProvider);
        mInstance = instance;
    }

    @Override
    public void initialize(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mResources = ApiHelper.getLibResources(mInstance.getContext());
        // Inflate MediaControlView2 from XML
        mRoot = makeControllerView();
        mInstance.addView(mRoot);
    }

    @Override
    public void setMediaSessionToken_impl(SessionToken2 token) {
        // TODO: implement this
    }

    @Override
    public void setOnFullScreenListener_impl(MediaControlView2.OnFullScreenListener l) {
        // TODO: implement this
    }

    @Override
    public void setController_impl(MediaController controller) {
        mController = controller;
        if (controller != null) {
            mControls = controller.getTransportControls();
            // Set mMetadata and mPlaybackState to existing MediaSession variables since they may
            // be called before the callback is called
            mPlaybackState = mController.getPlaybackState();
            mMetadata = mController.getMetadata();
            updateDuration();
            updateTitle();

            mController.registerCallback(new MediaControllerCallback());
        }
    }

    @Override
    public void setButtonVisibility_impl(int button, int visibility) {
        // TODO: add member variables for Fast-Forward/Prvious/Rewind buttons to save visibility in
        // order to prevent being overriden inside updateLayout().
        switch (button) {
            case MediaControlView2.BUTTON_PLAY_PAUSE:
                if (mPlayPauseButton != null && canPause()) {
                    mPlayPauseButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_FFWD:
                if (mFfwdButton != null && canSeekForward()) {
                    mFfwdButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_REW:
                if (mRewButton != null && canSeekBackward()) {
                    mRewButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_NEXT:
                if (mNextButton != null) {
                    mNextButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_PREV:
                if (mPrevButton != null) {
                    mPrevButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_SUBTITLE:
                if (mSubtitleButton != null && mSubtitleTrackCount > 0) {
                    mSubtitleButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_FULL_SCREEN:
                if (mFullScreenButton != null) {
                    mFullScreenButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_OVERFLOW:
                if (mOverflowButtonRight != null) {
                    mOverflowButtonRight.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_MUTE:
                if (mMuteButton != null) {
                    mMuteButton.setVisibility(visibility);
                }
                break;
            case MediaControlView2.BUTTON_SETTINGS:
                if (mSettingsButton != null) {
                    mSettingsButton.setVisibility(visibility);
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void requestPlayButtonFocus_impl() {
        if (mPlayPauseButton != null) {
            mPlayPauseButton.requestFocus();
        }
    }

    @Override
    public CharSequence getAccessibilityClassName_impl() {
        return MediaControlView2.class.getName();
    }

    @Override
    public boolean onTouchEvent_impl(MotionEvent ev) {
        return false;
    }

    // TODO: Should this function be removed?
    @Override
    public boolean onTrackballEvent_impl(MotionEvent ev) {
        return false;
    }

    @Override
    public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure_impl(widthMeasureSpec, heightMeasureSpec);

        // Update layout when this view's width changes in order to avoid any UI overlap between
        // transport controls.
        if (mPrevWidth != mInstance.getMeasuredWidth()
                || mPrevHeight != mInstance.getMeasuredHeight() || mNeedUXUpdate) {
            // Dismiss SettingsWindow if it is showing.
            mSettingsWindow.dismiss();

            // These views may not have been initialized yet.
            if (mTransportControls.getWidth() == 0 || mTimeView.getWidth() == 0) {
                return;
            }

            int currWidth = mInstance.getMeasuredWidth();
            int currHeight = mInstance.getMeasuredHeight();
            WindowManager manager = (WindowManager) mInstance.getContext().getApplicationContext()
                    .getSystemService(Context.WINDOW_SERVICE);
            Point screenSize = new Point();
            manager.getDefaultDisplay().getSize(screenSize);
            int screenWidth = screenSize.x;
            int screenHeight = screenSize.y;
            int fullIconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_full_icon_size);
            int embeddedIconSize = mResources.getDimensionPixelSize(
                    R.dimen.mcv2_embedded_icon_size);
            int marginSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_margin);

            // TODO: add support for Advertisement Mode.
            if (mMediaType == MEDIA_TYPE_DEFAULT) {
                // Max number of icons inside BottomBarRightView for Music mode is 4.
                int maxIconCount = 4;
                updateLayout(maxIconCount, fullIconSize, embeddedIconSize, marginSize, currWidth,
                        currHeight, screenWidth, screenHeight);
            } else if (mMediaType == MEDIA_TYPE_MUSIC) {
                if (mNeedUXUpdate) {
                    // One-time operation for Music media type
                    mBasicControls.removeView(mMuteButton);
                    mExtraControls.addView(mMuteButton, 0);
                    mVideoQualityButton.setVisibility(View.GONE);
                    if (mFfwdButton != null) {
                        mFfwdButton.setVisibility(View.GONE);
                    }
                    if (mRewButton != null) {
                        mRewButton.setVisibility(View.GONE);
                    }
                }
                mNeedUXUpdate = false;

                // Max number of icons inside BottomBarRightView for Music mode is 3.
                int maxIconCount = 3;
                updateLayout(maxIconCount, fullIconSize, embeddedIconSize, marginSize, currWidth,
                        currHeight, screenWidth, screenHeight);
            }
            mPrevWidth = currWidth;
            mPrevHeight = currHeight;
        }
        // TODO: move this to a different location.
        // Update title bar parameters in order to avoid overlap between title view and the right
        // side of the title bar.
        updateTitleBarLayout();
    }

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

        // TODO: Merge the below code with disableUnsupportedButtons().
        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);
        }
        disableUnsupportedButtons();
    }

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

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

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

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

    private boolean isPlaying() {
        if (mPlaybackState != null) {
            return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
        }
        return false;
    }

    private int getCurrentPosition() {
        mPlaybackState = mController.getPlaybackState();
        if (mPlaybackState != null) {
            return (int) mPlaybackState.getPosition();
        }
        return 0;
    }

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

    private boolean canPause() {
        if (mPlaybackState != null) {
            return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
        }
        return true;
    }

    private boolean canSeekBackward() {
        if (mPlaybackState != null) {
            return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
        }
        return true;
    }

    private boolean canSeekForward() {
        if (mPlaybackState != null) {
            return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
        }
        return true;
    }

    /**
     * Create the view that holds the widgets that control playback.
     * Derived classes can override this to create their own.
     *
     * @return The controller view.
     * @hide This doesn't work as advertised
     */
    protected ViewGroup makeControllerView() {
        ViewGroup root = (ViewGroup) ApiHelper.inflateLibLayout(mInstance.getContext(),
                R.layout.media_controller);
        initControllerView(root);
        return root;
    }

    private void initControllerView(ViewGroup v) {
        // 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 Extra View
        mMinimalExtraView = (LinearLayout) v.findViewById(R.id.minimal_extra_view);
        LinearLayout.LayoutParams params =
                (LinearLayout.LayoutParams) mMinimalExtraView.getLayoutParams();
        int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_embedded_icon_size);
        int marginSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_margin);
        params.setMargins(0, (iconSize + marginSize * 2) * (-1), 0, 0);
        mMinimalExtraView.setLayoutParams(params);
        mMinimalExtraView.setVisibility(View.GONE);

        // Relating to Progress Bar View
        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));
            }
            mProgress.setMax(MAX_PROGRESS);
        }
        mProgressBuffer = v.findViewById(R.id.progress_buffer);

        // 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
        mBottomBarRightView = v.findViewById(R.id.bottom_bar_right);
        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);
            // TODO: Show Fullscreen button when only it is possible.
        }
        mOverflowButtonRight = v.findViewById(R.id.overflow_right);
        if (mOverflowButtonRight != null) {
            mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
        }
        mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
        if (mOverflowButtonLeft != null) {
            mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
        }
        mMuteButton = v.findViewById(R.id.mute);
        if (mMuteButton != null) {
            mMuteButton.setOnClickListener(mMuteButtonListener);
        }
        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) ApiHelper.inflateLibLayout(mInstance.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);
        mEmbeddedSettingsItemHeight = mResources.getDimensionPixelSize(
                R.dimen.mcv2_embedded_settings_height);
        mFullSettingsItemHeight = mResources.getDimensionPixelSize(
                R.dimen.mcv2_full_settings_height);
        mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
                R.dimen.mcv2_settings_offset);
        mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
                ViewGroup.LayoutParams.WRAP_CONTENT, true);
    }

    /**
     * Disable pause or seek buttons if the stream cannot be paused or seeked.
     * This requires the control interface to be a MediaPlayerControlExt
     */
    private void disableUnsupportedButtons() {
        try {
            if (mPlayPauseButton != null && !canPause()) {
                mPlayPauseButton.setEnabled(false);
            }
            if (mRewButton != null && !canSeekBackward()) {
                mRewButton.setEnabled(false);
            }
            if (mFfwdButton != null && !canSeekForward()) {
                mFfwdButton.setEnabled(false);
            }
            // TODO What we really should do is add a canSeek to the MediaPlayerControl interface;
            // this scheme can break the case when applications want to allow seek through the
            // progress bar but disable forward/backward buttons.
            //
            // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE,
            // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue
            // shouldn't arise in existing applications.
            if (mProgress != null && !canSeekBackward() && !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.
        }
    }

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

    private String stringForTime(int timeMs) {
        int totalSeconds = timeMs / 1000;

        int seconds = totalSeconds % 60;
        int minutes = (totalSeconds / 60) % 60;
        int 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();
        }
    }

    private int setProgress() {
        if (mController == null || mDragging) {
            return 0;
        }
        int positionOnProgressBar = 0;
        int currentPosition = getCurrentPosition();
        if (mDuration > 0) {
            positionOnProgressBar = (int) (MAX_PROGRESS * (long) 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 (getBufferPercentage() < 0) {
                mProgress.setSecondaryProgress(MAX_PROGRESS);
            } else {
                mProgress.setSecondaryProgress(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.MediaControlView2_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) {
                int remainingTime =
                        (mDuration - currentPosition < 0) ? 0 : (mDuration - currentPosition);
                String remainingTimeText = mResources.getString(
                        R.string.MediaControlView2_ad_remaining_time,
                        stringForTime(remainingTime));
                mAdRemainingView.setText(remainingTimeText);
            }
        }
        return currentPosition;
    }

    private void togglePausePlayState() {
        if (isPlaying()) {
            mControls.pause();
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_play_button_desc));
        } else {
            mControls.play();
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_pause_circle_filled, null));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_pause_button_desc));
        }
    }

    // There are two scenarios that can trigger the seekbar listener to trigger:
    //
    // The first is the user using the touchpad to adjust the posititon 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.
            mInstance.removeCallbacks(mUpdateProgress);

            // 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) {
                mPlayPauseButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
                mPlayPauseButton.setContentDescription(
                        mResources.getString(R.string.mcv2_play_button_desc));
                mIsStopped = false;
            }
        }

        @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;
            }
            if (mDuration > 0) {
                int position = (int) (((long) mDuration * progress) / MAX_PROGRESS);
                mControls.seekTo(position);

                if (mCurrentTime != null) {
                    mCurrentTime.setText(stringForTime(position));
                }
            }
        }

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

            setProgress();

            // Ensure that progress is properly updated in the future,
            // the call to show() does not guarantee this because it is a
            // no-op if we are already showing.
            mInstance.post(mUpdateProgress);
        }
    };

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

    private final View.OnClickListener mRewListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int pos = getCurrentPosition() - REWIND_TIME_MS;
            mControls.seekTo(pos);
            setProgress();
        }
    };

    private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int pos = getCurrentPosition() + FORWARD_TIME_MS;
            mControls.seekTo(pos);
            setProgress();
        }
    };

    private final View.OnClickListener mNextListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mControls.skipToNext();
        }
    };

    private final View.OnClickListener mPrevListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mControls.skipToPrevious();
        }
    };

    private final View.OnClickListener mBackListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO: implement
        }
    };

    private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
            mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
            mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
            displaySettingsWindow(mSubSettingsAdapter);
        }
    };

    private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
            mSubSettingsAdapter.setTexts(mVideoQualityList);
            mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
            displaySettingsWindow(mSubSettingsAdapter);
        }
    };

    private final View.OnClickListener mFullScreenListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final boolean isEnteringFullScreen = !mIsFullScreen;
            // TODO: Re-arrange the button layouts according to the UX.
            if (isEnteringFullScreen) {
                mFullScreenButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_fullscreen_exit, null));
            } else {
                mFullScreenButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_fullscreen, null));
            }
            Bundle args = new Bundle();
            args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
            mController.sendCommand(COMMAND_SET_FULLSCREEN, args, null);

            mIsFullScreen = isEnteringFullScreen;
        }
    };

    private final View.OnClickListener mOverflowRightListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mBasicControls.setVisibility(View.GONE);
            mExtraControls.setVisibility(View.VISIBLE);
        }
    };

    private final View.OnClickListener mOverflowLeftListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mBasicControls.setVisibility(View.VISIBLE);
            mExtraControls.setVisibility(View.GONE);
        }
    };

    private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!mIsMute) {
                mMuteButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_mute, null));
                mMuteButton.setContentDescription(
                        mResources.getString(R.string.mcv2_muted_button_desc));
                mIsMute = true;
                mController.sendCommand(COMMAND_MUTE, null, null);
            } else {
                mMuteButton.setImageDrawable(
                        mResources.getDrawable(R.drawable.ic_unmute, null));
                mMuteButton.setContentDescription(
                        mResources.getString(R.string.mcv2_unmuted_button_desc));
                mIsMute = false;
                mController.sendCommand(COMMAND_UNMUTE, null, null);
            }
        }
    };

    private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            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;
                    } else if (position == SETTINGS_MODE_HELP) {
                        // TODO: implement this.
                        mSettingsWindow.dismiss();
                        return;
                    }
                    displaySettingsWindow(mSubSettingsAdapter);
                    break;
                case SETTINGS_MODE_AUDIO_TRACK:
                    if (position != mSelectedAudioTrackIndex) {
                        mSelectedAudioTrackIndex = position;
                        if (mAudioTrackCount > 0) {
                            Bundle extra = new Bundle();
                            extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
                            mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
                        }
                        mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
                                mSubSettingsAdapter.getMainText(position));
                    }
                    mSettingsWindow.dismiss();
                    break;
                case SETTINGS_MODE_PLAYBACK_SPEED:
                    if (position != mSelectedSpeedIndex) {
                        mSelectedSpeedIndex = position;
                        Bundle extra = new Bundle();
                        extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
                        mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
                        mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
                                mSubSettingsAdapter.getMainText(position));
                    }
                    mSettingsWindow.dismiss();
                    break;
                case SETTINGS_MODE_HELP:
                    // TODO: implement this.
                    break;
                case SETTINGS_MODE_SUBTITLE_TRACK:
                    if (position != mSelectedSubtitleTrackIndex) {
                        mSelectedSubtitleTrackIndex = position;
                        if (position > 0) {
                            Bundle extra = new Bundle();
                            extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
                            mController.sendCommand(
                                    MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
                            mSubtitleButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_subtitle_on, null));
                            mSubtitleButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_cc_is_on));
                            mSubtitleIsEnabled = true;
                        } else {
                            mController.sendCommand(
                                    MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
                            mSubtitleButton.setImageDrawable(
                                    mResources.getDrawable(R.drawable.ic_subtitle_off, null));
                            mSubtitleButton.setContentDescription(
                                    mResources.getString(R.string.mcv2_cc_is_off));

                            mSubtitleIsEnabled = false;
                        }
                    }
                    mSettingsWindow.dismiss();
                    break;
                case SETTINGS_MODE_VIDEO_QUALITY:
                    // TODO: add support for video quality
                    mSelectedVideoQualityIndex = position;
                    mSettingsWindow.dismiss();
                    break;
            }
        }
    };

    private void updateDuration() {
        if (mMetadata != null) {
            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                mDuration = (int) mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
                // update progress bar
                setProgress();
            }
        }
    }

    private void updateTitle() {
        if (mMetadata != null) {
            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
                mTitleView.setText(mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
            }
        }
    }

    // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are
    // greater than the length of the title bar, reduce the size of the left bar (which makes the
    // TextView that contains the title of the media file shrink).
    private void updateTitleBarLayout() {
        if (mTitleBar != null) {
            int titleBarWidth = mTitleBar.getWidth();

            View leftBar = mTitleBar.findViewById(R.id.title_bar_left);
            View rightBar = mTitleBar.findViewById(R.id.title_bar_right);
            int leftBarWidth = leftBar.getWidth();
            int rightBarWidth = rightBar.getWidth();

            RelativeLayout.LayoutParams params =
                    (RelativeLayout.LayoutParams) leftBar.getLayoutParams();
            if (leftBarWidth + rightBarWidth > titleBarWidth) {
                params.width = titleBarWidth - rightBarWidth;
                mOriginalLeftBarWidth = leftBarWidth;
            } else if (leftBarWidth + rightBarWidth < titleBarWidth && mOriginalLeftBarWidth != 0) {
                params.width = mOriginalLeftBarWidth;
                mOriginalLeftBarWidth = 0;
            }
            leftBar.setLayoutParams(params);
        }
    }

    private void updateAudioMetadata() {
        if (mMediaType != MEDIA_TYPE_MUSIC) {
            return;
        }

        if (mMetadata != null) {
            String titleText = "";
            String artistText = "";
            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
                titleText = mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
            } else {
                titleText = mResources.getString(R.string.mcv2_music_title_unknown_text);
            }

            if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
                artistText = mMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
            } else {
                artistText = mResources.getString(R.string.mcv2_music_artist_unknown_text);
            }

            // Update title for Embedded size type
            mTitleView.setText(titleText + " - " + artistText);

            // Set to true to update layout inside onMeasure()
            mNeedUXUpdate = true;
        }
    }

    private void updateLayout() {
        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 maxIconCount, int fullIconSize, int embeddedIconSize,
             int marginSize, int currWidth, int currHeight, int screenWidth, int screenHeight) {
        int fullBottomBarRightWidthMax = fullIconSize * maxIconCount
                + marginSize * (maxIconCount * 2);
        int embeddedBottomBarRightWidthMax = embeddedIconSize * maxIconCount
                + marginSize * (maxIconCount * 2);
        int fullWidth = mTransportControls.getWidth() + mTimeView.getWidth()
                + fullBottomBarRightWidthMax;
        int embeddedWidth = mTimeView.getWidth() + embeddedBottomBarRightWidthMax;
        int screenMaxLength = Math.max(screenWidth, screenHeight);

        if (fullWidth > screenMaxLength) {
            // TODO: screen may be smaller than the length needed for Full size.
        }

        boolean isFullSize = (mMediaType == MEDIA_TYPE_DEFAULT) ? (currWidth == screenMaxLength) :
                (currWidth == screenWidth && currHeight == screenHeight);

        if (isFullSize) {
            if (mSizeType != SIZE_TYPE_FULL) {
                updateLayoutForSizeChange(SIZE_TYPE_FULL);
                if (mMediaType == MEDIA_TYPE_MUSIC) {
                    mTitleView.setVisibility(View.GONE);
                }
            }
        } 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);
                }
            }
        }
    }

    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);

                // Relating to Full Screen Button
                mMinimalExtraView.setVisibility(View.GONE);
                mFullScreenButton = mBottomBarRightView.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));
                mProgressBuffer.setVisibility(View.VISIBLE);

                // Relating to Bottom Bar
                mBottomBar.setVisibility(View.VISIBLE);
                if (timeViewParams.getRule(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);

                // Relating to Full Screen Button
                mMinimalExtraView.setVisibility(View.GONE);
                mFullScreenButton = mBottomBarRightView.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));
                mProgressBuffer.setVisibility(View.VISIBLE);

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

                // Relating to Full Screen Button
                mMinimalExtraView.setVisibility(View.VISIBLE);
                mFullScreenButton = mMinimalExtraView.findViewById(R.id.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);

        if (isPlaying()) {
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_pause_circle_filled, null));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_pause_button_desc));
        } else {
            mPlayPauseButton.setImageDrawable(
                    mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
            mPlayPauseButton.setContentDescription(
                    mResources.getString(R.string.mcv2_play_button_desc));
        }

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

    private View inflateTransportControls(int layoutId) {
        View v = ApiHelper.inflateLibLayout(mInstance.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) {
            mFfwdButton.setOnClickListener(mFfwdListener);
            if (mMediaType == MEDIA_TYPE_MUSIC) {
                mFfwdButton.setVisibility(View.GONE);
            }
        }
        mRewButton = v.findViewById(R.id.rew);
        if (mRewButton != null) {
            mRewButton.setOnClickListener(mRewListener);
            if (mMediaType == MEDIA_TYPE_MUSIC) {
                mRewButton.setVisibility(View.GONE);
            }
        }
        // TODO: Add support for Next and Previous buttons
        mNextButton = v.findViewById(R.id.next);
        if (mNextButton != null) {
            mNextButton.setOnClickListener(mNextListener);
            mNextButton.setVisibility(View.GONE);
        }
        mPrevButton = v.findViewById(R.id.prev);
        if (mPrevButton != null) {
            mPrevButton.setOnClickListener(mPrevListener);
            mPrevButton.setVisibility(View.GONE);
        }
        return v;
    }

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

        mSettingsSubTextsList = new ArrayList<String>();
        mSettingsSubTextsList.add(
                mResources.getString(R.string.MediaControlView2_audio_track_none_text));
        mSettingsSubTextsList.add(
                mResources.getStringArray(
                        R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
        mSettingsSubTextsList.add(RESOURCE_EMPTY);

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

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

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

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

        mPlaybackSpeedList = new ArrayList<Float>();
        int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
        for (int i = 0; i < speeds.length; i++) {
            float speed = (float) speeds[i] / 100.0f;
            mPlaybackSpeedList.add(speed);
        }
    }

    private 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 and show
        int itemHeight = (mSizeType == SIZE_TYPE_EMBEDDED)
                ? mEmbeddedSettingsItemHeight : mFullSettingsItemHeight;
        int totalHeight = adapter.getCount() * itemHeight;
        mSettingsWindow.dismiss();
        mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
                mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
    }

    private class MediaControllerCallback extends MediaController.Callback {
        @Override
        public void onPlaybackStateChanged(PlaybackState 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.getState() != mPrevState) {
                switch (mPlaybackState.getState()) {
                    case PlaybackState.STATE_PLAYING:
                        mPlayPauseButton.setImageDrawable(
                                mResources.getDrawable(R.drawable.ic_pause_circle_filled, null));
                        mPlayPauseButton.setContentDescription(
                                mResources.getString(R.string.mcv2_pause_button_desc));
                        mInstance.removeCallbacks(mUpdateProgress);
                        mInstance.post(mUpdateProgress);
                        break;
                    case PlaybackState.STATE_PAUSED:
                        mPlayPauseButton.setImageDrawable(
                                mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
                        mPlayPauseButton.setContentDescription(
                                mResources.getString(R.string.mcv2_play_button_desc));
                        break;
                    case PlaybackState.STATE_STOPPED:
                        mPlayPauseButton.setImageDrawable(
                                mResources.getDrawable(R.drawable.ic_replay_circle_filled, null));
                        mPlayPauseButton.setContentDescription(
                                mResources.getString(R.string.mcv2_replay_button_desc));
                        mIsStopped = true;
                        break;
                    default:
                        break;
                }
                mPrevState = mPlaybackState.getState();
            }

            if (mPlaybackActions != mPlaybackState.getActions()) {
                long newActions = mPlaybackState.getActions();
                if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
                    mPlayPauseButton.setVisibility(View.VISIBLE);
                }
                if ((newActions & PlaybackState.ACTION_REWIND) != 0
                        && mMediaType != MEDIA_TYPE_MUSIC) {
                    if (mRewButton != null) {
                        mRewButton.setVisibility(View.VISIBLE);
                    }
                }
                if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0
                        && mMediaType != MEDIA_TYPE_MUSIC) {
                    if (mFfwdButton != null) {
                        mFfwdButton.setVisibility(View.VISIBLE);
                    }
                }
                if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
                    mSeekAvailable = true;
                } else {
                    mSeekAvailable = false;
                }
                mPlaybackActions = newActions;
            }

            // Add buttons if custom actions are present.
            List<PlaybackState.CustomAction> customActions = mPlaybackState.getCustomActions();
            mCustomButtons.removeAllViews();
            if (customActions.size() > 0) {
                for (PlaybackState.CustomAction action : customActions) {
                    ImageButton button = new ImageButton(mInstance.getContext(),
                            null /* AttributeSet */, 0 /* Style */);
                    // TODO: Apply R.style.BottomBarButton to this button using library context.
                    // Refer Constructor with argument (int defStyleRes) of View.java
                    button.setImageResource(action.getIcon());
                    button.setTooltipText(action.getName());
                    final String actionString = action.getAction().toString();
                    button.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // TODO: Currently, we are just sending extras that came from session.
                            // Is it the right behavior?
                            mControls.sendCustomAction(actionString, action.getExtras());
                            mInstance.setVisibility(View.VISIBLE);
                        }
                    });
                    mCustomButtons.addView(button);
                }
            }
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            mMetadata = metadata;
            updateDuration();
            updateTitle();
            updateAudioMetadata();
        }

        @Override
        public void onSessionEvent(String event, Bundle extras) {
            switch (event) {
                case EVENT_UPDATE_TRACK_STATUS:
                    mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
                    // 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 = extras.getInt(KEY_AUDIO_TRACK_COUNT);
                    mAudioTrackList = new ArrayList<String>();
                    if (mAudioTrackCount > 0) {
                        // TODO: add more text about track info.
                        for (int i = 0; i < mAudioTrackCount; i++) {
                            String track = mResources.getString(
                                    R.string.MediaControlView2_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.MediaControlView2_audio_track_none_text));
                    }
                    if (mVideoTrackCount == 0 && mAudioTrackCount > 0) {
                        mMediaType = MEDIA_TYPE_MUSIC;
                    }

                    mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
                    mSubtitleDescriptionsList = new ArrayList<String>();
                    if (mSubtitleTrackCount > 0) {
                        mSubtitleButton.setVisibility(View.VISIBLE);
                        mSubtitleButton.setEnabled(true);
                        mSubtitleDescriptionsList.add(mResources.getString(
                                R.string.MediaControlView2_subtitle_off_text));
                        for (int i = 0; i < mSubtitleTrackCount; i++) {
                            String track = mResources.getString(
                                    R.string.MediaControlView2_subtitle_track_number_text, i + 1);
                            mSubtitleDescriptionsList.add(track);
                        }
                    } else {
                        mSubtitleButton.setVisibility(View.GONE);
                        mSubtitleButton.setEnabled(false);
                    }
                    break;
                case EVENT_UPDATE_MEDIA_TYPE_STATUS:
                    boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
                    if (newStatus != mIsAdvertisement) {
                        mIsAdvertisement = newStatus;
                        updateLayout();
                    }
                    break;
            }
        }
    }

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

        public 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
            // TODO: implement this.
            return 0;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            View row;
            if (mSizeType == SIZE_TYPE_FULL) {
                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
                        R.layout.full_settings_list_item);
            } else {
                row = ApiHelper.inflateLibLayout(mInstance.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 || mSubTexts.get(position) == RESOURCE_EMPTY) {
                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), null));
            }
            return row;
        }

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

    // TODO: extend this class from SettingsAdapter
    private class SubSettingsAdapter extends BaseAdapter {
        private List<String> mTexts;
        private int mCheckPosition;

        public 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
            // TODO: implement this.
            return 0;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            View row;
            if (mSizeType == SIZE_TYPE_FULL) {
                row = ApiHelper.inflateLibLayout(mInstance.getContext(),
                        R.layout.full_sub_settings_list_item);
            } else {
                row = ApiHelper.inflateLibLayout(mInstance.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;
        }
    }
}
