com.aengbee.android.leanback.ui.PlaybackOverlayCustomFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.aengbee.android.leanback.ui.PlaybackOverlayCustomFragment.java

Source

/*
 * Copyright (c) 2014 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.aengbee.android.leanback.ui;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS;
import static com.aengbee.android.leanback.utils.ConvertEntoKo.concatOldNew;
import static com.aengbee.android.leanback.utils.ConvertEntoKo.engToKor;
import static com.aengbee.android.leanback.utils.ConvertEntoKo.splitKor2Eng;

import com.aengbee.android.leanback.BuildConfig;
import com.aengbee.android.leanback.card.presenters.CardPresenterSelector;
import com.aengbee.android.leanback.model.Card;
import com.aengbee.android.leanback.model.CardRow;
import com.aengbee.android.leanback.utils.CardListRow;
import com.aengbee.android.leanback.utils.InputTables;
import com.aengbee.android.leanback.utils.KoreanAutomata;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.util.Util;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.LoaderManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.support.v17.leanback.app.*;
import android.support.v17.leanback.app.SearchFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.CursorObjectAdapter;
import android.support.v17.leanback.widget.DividerRow;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SearchBar;
import android.support.v17.leanback.widget.SectionRow;
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.CompletionInfo;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.aengbee.android.leanback.R;
import com.aengbee.android.leanback.utils.Utils;
import com.aengbee.android.leanback.data.VideoContract;
import com.aengbee.android.leanback.model.Video;
import com.aengbee.android.leanback.model.VideoCursorMapper;
import com.aengbee.android.leanback.player.ExtractorRendererBuilder;
import com.aengbee.android.leanback.player.VideoPlayer;
import com.aengbee.android.leanback.presenter.CardPresenter;
import com.google.gson.Gson;
import com.aengbee.android.leanback.utils.ConvertEntoKo;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/*
 * The PlaybackOverlayFragment class handles the Fragment associated with displaying the UI for the
 * media controls such as play / pause / skip forward / skip backward etc.
 *
 * The UI is updated through events that it receives from its MediaController
 */
public class PlaybackOverlayCustomFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment
        implements LoaderManager.LoaderCallbacks<Cursor>, TextureView.SurfaceTextureListener, VideoPlayer.Listener {
    private static final String TAG = "PlaybackOverlayFragment";
    private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_NONE;
    private static final String AUTO_PLAY = "auto_play";
    private static final Bundle mAutoPlayExtras = new Bundle();
    private static final int RECOMMENDED_VIDEOS_LOADER = 1;
    private static final int QUEUE_VIDEOS_LOADER = 2;

    private static final String SPECIAL_CHARACTERS = "`~!@#$%^&*()-_=+|\\;:'\",<.>/?";
    private KoreanAutomata kauto;
    private static StringBuilder mComposing = new StringBuilder();
    public static int currentKeyboard = 0;
    private static final int mKoreanKeyboard = 0;
    private static final int mQwertyKeyboard = 1;
    private static final int mSymbolsKeyboard = 2;
    public static String mQuery;

    static {
        mAutoPlayExtras.putBoolean(AUTO_PLAY, true);
    }

    private final VideoCursorMapper mVideoCursorMapper = new VideoCursorMapper();
    private int mSpecificVideoLoaderId = 3;
    private int mQueueIndex = -1;
    private Video mSelectedVideo; // Video is the currently playing Video and its metadata.
    private ArrayObjectAdapter mRowsAdapter;
    private List<MediaSessionCompat.QueueItem> mQueue = new ArrayList<>();
    private CursorObjectAdapter mVideoCursorAdapter;
    private MediaSessionCompat mSession; // MediaSession is used to hold the state of our media playback.
    private LoaderManager.LoaderCallbacks<Cursor> mCallbacks;
    private MediaController mMediaController;
    private PlaybackControlHelper mGlue;
    private MediaController.Callback mMediaControllerCallback;
    private VideoPlayer mPlayer;
    private boolean mIsMetadataSet = false;
    private AudioManager mAudioManager;
    private boolean mHasAudioFocus;
    private boolean mPauseTransient;
    private final AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
        @Override
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
            case AudioManager.AUDIOFOCUS_LOSS:
                abandonAudioFocus();
                pause();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                if (mGlue.isMediaPlaying()) {
                    pause();
                    mPauseTransient = true;
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                mPlayer.mute(true);
                break;
            case AudioManager.AUDIOFOCUS_GAIN:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                if (mPauseTransient) {
                    play();
                }
                mPlayer.mute(false);
                break;
            }
        }
    };

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mCallbacks = this;

        createMediaSession();
    }

    @Override
    public void onStop() {
        super.onStop();

        mSession.release();
        releasePlayer();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mMediaController != null) {
            mMediaController.unregisterCallback(mMediaControllerCallback);
        }
        mSession.release();
        releasePlayer();
    }

    @Override
    public void onStart() {
        super.onStart();

        // Set up UI
        Video video = getActivity().getIntent().getParcelableExtra(VideoDetailsActivity.VIDEO);
        if (!updateSelectedVideo(video)) {
            return;
        }

        //mGlue = new PlaybackControlHelper(getActivity(), this, mSelectedVideo);
        PlaybackControlsRowPresenter controlsRowPresenter = mGlue.createControlsRowAndPresenter();
        PlaybackControlsRow controlsRow = mGlue.getControlsRow();
        mMediaControllerCallback = mGlue.createMediaControllerCallback();

        mMediaController = getActivity().getMediaController();
        mMediaController.registerCallback(mMediaControllerCallback);

        ClassPresenterSelector ps = new ClassPresenterSelector();

        ps.addClassPresenter(PlaybackControlsRow.class, controlsRowPresenter);
        ps.addClassPresenter(CardListRow.class, new ListRowPresenter());
        ps.addClassPresenter(ListRow.class, new ListRowPresenter());

        kauto = new KoreanAutomata();
        if (!kauto.IsKoreanMode())
            kauto.ToggleMode();

        mRowsAdapter = new ArrayObjectAdapter(ps);
        //mRowsAdapter.add(controlsRow);
        mRowsAdapter.add(createRows(currentKeyboard));
        addOtherRows();
        //updatePlaybackRow();
        setAdapter(mRowsAdapter);

        startPlaying();

        getRowsFragment().setAlignment(150);

    }

    @Override
    public void onResume() {
        super.onResume();
        Video video = getActivity().getIntent().getParcelableExtra(VideoDetailsActivity.VIDEO);
        if (!updateSelectedVideo(video)) {
            return;
        }

        startPlaying();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);

        // Initialize instance variables.
        TextureView textureView = (TextureView) getActivity().findViewById(R.id.texture_view);
        textureView.setSurfaceTextureListener(this);

        setBackgroundType(BACKGROUND_TYPE);
        // Set up listener.
        setOnItemViewClickedListener(new ItemViewClickedListener());

    }

    private boolean updateSelectedVideo(Video video) {
        Intent intent = new Intent(getActivity().getIntent());
        intent.putExtra(VideoDetailsActivity.VIDEO, video);
        if (mSelectedVideo != null && mSelectedVideo.equals(video)) {
            return false;
        }
        mSelectedVideo = video;

        PendingIntent pi = PendingIntent.getActivity(getActivity(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        mSession.setSessionActivity(pi);

        return true;
    }

    @TargetApi(VERSION_CODES.N)
    @Override
    public void onPause() {
        super.onPause();
        if (mGlue.isMediaPlaying()) {
            boolean isVisibleBehind = getActivity().requestVisibleBehind(true);
            boolean isInPictureInPictureMode = PlaybackOverlayActivity.supportsPictureInPicture(getActivity())
                    && getActivity().isInPictureInPictureMode();
            if (!isVisibleBehind && !isInPictureInPictureMode) {
                pause();
            }
        } else {
            getActivity().requestVisibleBehind(false);
        }

    }

    @Override
    public void onPictureInPictureModeChanged(boolean pictureInPictureMode) {
        if (pictureInPictureMode) {
            mGlue.setFadingEnabled(false);
            setFadingEnabled(true);
            fadeOut();
        } else {
            mGlue.setFadingEnabled(true);
        }
    }

    public String queryNormalizer(String query) {
        String normalized = "";
        if (query != null && !query.isEmpty()) {
            normalized = query.replaceAll(SPECIAL_CHARACTERS, "").toLowerCase();
            normalized = normalized.replace(" ", "");
        }

        return "%" + normalized + "%";
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        switch (id) {
        case RECOMMENDED_VIDEOS_LOADER: {
            String category = args.getString(VideoContract.VideoEntry.COLUMN_CATEGORY);
            String query = args.getString(VideoContract.VideoEntry.COLUMN_DESC, "");
            Loader<Cursor> c;
            if (query.contains(" ")) {
                c = new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                        //VideoContract.VideoEntry.COLUMN_CATEGORY + " = ? AND " +
                        " ( lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC
                                + " , ? ) ) LIKE ? ) AND " + VideoContract.VideoEntry.COLUMN_RATING_SCORE
                                + " == ? AND " + VideoContract.VideoEntry.COLUMN_CATEGORY + " == ? ",
                        // Selection clause is category.
                        new String[] { SPECIAL_CHARACTERS, "%" + query + "%", SPECIAL_CHARACTERS, "%" + query + "%",
                                SPECIAL_CHARACTERS, queryNormalizer(query), SPECIAL_CHARACTERS,
                                queryNormalizer(query), "1", "KY" },
                        VideoContract.VideoEntry.COLUMN_NAME// Default sort order
                );
            } else {
                c = new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                        //VideoContract.VideoEntry.COLUMN_CATEGORY + " = ? AND " +
                        " ( lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC
                                + " , ? ) ) LIKE ? ) AND " + VideoContract.VideoEntry.COLUMN_RATING_SCORE
                                + " == ? AND " + VideoContract.VideoEntry.COLUMN_CATEGORY + " == ? ",
                        // Selection clause is category.
                        new String[] { SPECIAL_CHARACTERS, "%" + query + "%", SPECIAL_CHARACTERS, "%" + query + "%",
                                "1", "KY" },
                        VideoContract.VideoEntry.COLUMN_NAME// Default sort order
                );
            }

            return c;
        }
        case QUEUE_VIDEOS_LOADER: {
            String category = args.getString(VideoContract.VideoEntry.COLUMN_CATEGORY);
            return new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                    VideoContract.VideoEntry.COLUMN_CATEGORY + " = ?",
                    // Selection clause is category.
                    new String[] { category }, // Select based on the category.
                    null // Default sort order
            );
        }
        case 0: {
            // Loading a specific video.
            String videoId = args.getString(VideoContract.VideoEntry._ID);
            return new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                    VideoContract.VideoEntry._ID + " = ?", // Selection clause is id.
                    new String[] { videoId }, // Select based on the id.
                    null // Default sort order
            );

        }
        default: {
            String query = mQuery;
            Loader<Cursor> c;
            if (query.contains(" ")) {
                c = new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                        //VideoContract.VideoEntry.COLUMN_CATEGORY + " = ? AND " +
                        " ( lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC
                                + " , ? ) ) LIKE ? ) AND " + VideoContract.VideoEntry.COLUMN_RATING_SCORE
                                + " == ? AND " + VideoContract.VideoEntry.COLUMN_CATEGORY + " == ? ",
                        // Selection clause is category.
                        new String[] { SPECIAL_CHARACTERS, "%" + query + "%", SPECIAL_CHARACTERS, "%" + query + "%",
                                SPECIAL_CHARACTERS, queryNormalizer(query), SPECIAL_CHARACTERS,
                                queryNormalizer(query), "1", "KY" },
                        VideoContract.VideoEntry.COLUMN_NAME// Default sort order
                );
            } else {
                c = new CursorLoader(getActivity(), VideoContract.VideoEntry.CONTENT_URI, null, // Projection to return - null means return all fields.
                        //VideoContract.VideoEntry.COLUMN_CATEGORY + " = ? AND " +
                        " ( lower ( trim ( " + VideoContract.VideoEntry.COLUMN_NAME + " , ? ) ) LIKE ? OR "
                                + " lower ( trim ( " + VideoContract.VideoEntry.COLUMN_DESC
                                + " , ? ) ) LIKE ? ) AND " + VideoContract.VideoEntry.COLUMN_RATING_SCORE
                                + " == ? AND " + VideoContract.VideoEntry.COLUMN_CATEGORY + " == ? ",
                        // Selection clause is category.
                        new String[] { SPECIAL_CHARACTERS, "%" + query + "%", SPECIAL_CHARACTERS, "%" + query + "%",
                                "1", "KY" },
                        VideoContract.VideoEntry.COLUMN_NAME// Default sort order
                );
            }

            return c;
        }
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (cursor != null && cursor.moveToFirst()) {
            switch (loader.getId()) {
            case QUEUE_VIDEOS_LOADER: {
                mQueue.clear();
                while (!cursor.isAfterLast()) {
                    Video v = (Video) mVideoCursorMapper.convert(cursor);

                    // Set the queue index to the selected video.
                    if (v.id == mSelectedVideo.id) {
                        mQueueIndex = mQueue.size();
                    }

                    // Add the video to the queue.
                    MediaSessionCompat.QueueItem item = getQueueItem(v);
                    mQueue.add(item);

                    cursor.moveToNext();
                }

                mSession.setQueue(mQueue);
                mSession.setQueueTitle(getString(R.string.queue_name));
                break;
            }
            case RECOMMENDED_VIDEOS_LOADER: {
                mVideoCursorAdapter.changeCursor(cursor);
                break;
            }
            case 0: {
                /*
                int titleRes;
                if (cursor != null && cursor.moveToFirst()) {
                    mResultsFound = true;
                    titleRes = R.string.search_results;
                } else {
                    mResultsFound = false;
                    titleRes = R.string.no_search_results;
                }
                mVideoCursorAdapter.changeCursor(cursor);
                HeaderItem header = new HeaderItem(getString(titleRes, mQuery));
                mRowsAdapter.clear();
                ListRow row = new ListRow(header, mVideoCursorAdapter);
                mRowsAdapter.add(row);
                setAdapter(mRowsAdapter);
                break;
                */

                // Playing a specific video.
                Video video = (Video) mVideoCursorMapper.convert(cursor);
                playVideo(video, mAutoPlayExtras);
                break;
            }
            default: {

                mVideoCursorAdapter.changeCursor(cursor);

                break;
            }
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mVideoCursorAdapter.changeCursor(null);
    }

    private void setPosition(long position) {
        if (position > mPlayer.getDuration()) {
            mPlayer.seekTo(mPlayer.getDuration());
        } else if (position < 0) {
            mPlayer.seekTo(0L);
        } else {
            mPlayer.seekTo(position);
        }
    }

    private void createMediaSession() {
        if (mSession == null) {
            mSession = new MediaSessionCompat(getActivity(), "LeanbackSampleApp");
            mSession.setCallback(new MediaSessionCallback());
            mSession.setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS);
            mSession.setActive(true);

            // Set the Activity's MediaController used to invoke transport controls / adjust volume.
            try {
                ((FragmentActivity) getActivity()).setSupportMediaController(
                        new MediaControllerCompat(getActivity(), mSession.getSessionToken()));
                setPlaybackState(PlaybackState.STATE_NONE);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    private MediaSessionCompat.QueueItem getQueueItem(Video v) {
        MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder().setDescription(v.description)
                .setMediaId(v.id + "").setIconUri(Uri.parse(v.cardImageUrl)).setMediaUri(Uri.parse(v.videoUrl))
                .setSubtitle(v.studio).setTitle(v.title).build();
        return new MediaSessionCompat.QueueItem(desc, v.id);
    }

    public long getBufferedPosition() {
        if (mPlayer != null) {
            return mPlayer.getBufferedPosition();
        }
        return 0L;
    }

    public long getCurrentPosition() {
        if (mPlayer != null) {
            return mPlayer.getCurrentPosition();
        }
        return 0L;
    }

    public long getDuration() {
        if (mPlayer != null) {
            return mPlayer.getDuration();
        }
        return ExoPlayer.UNKNOWN_TIME;
    }

    private long getAvailableActions(int nextState) {
        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
                | PlaybackState.ACTION_PLAY_FROM_SEARCH | PlaybackState.ACTION_SKIP_TO_NEXT
                | PlaybackState.ACTION_SKIP_TO_PREVIOUS | PlaybackState.ACTION_FAST_FORWARD
                | PlaybackState.ACTION_REWIND | PlaybackState.ACTION_PAUSE;

        if (nextState == PlaybackState.STATE_PLAYING) {
            actions |= PlaybackState.ACTION_PAUSE;
        }

        return actions;
    }

    private void play() {
        // Request audio focus whenever we resume playback
        // because the app might have abandoned audio focus due to the AUDIOFOCUS_LOSS.
        requestAudioFocus();

        if (mPlayer == null) {
            setPlaybackState(PlaybackState.STATE_NONE);
            return;
        }
        if (!mGlue.isMediaPlaying()) {
            mPlayer.getPlayerControl().start();
            setPlaybackState(PlaybackState.STATE_PLAYING);
        }
    }

    private void pause() {
        mPauseTransient = false;

        if (mPlayer == null) {
            setPlaybackState(PlaybackState.STATE_NONE);
            return;
        }
        if (mGlue.isMediaPlaying()) {
            mPlayer.getPlayerControl().pause();
            setPlaybackState(PlaybackState.STATE_PAUSED);
        }
    }

    private void requestAudioFocus() {
        if (mHasAudioFocus) {
            return;
        }
        int result = mAudioManager.requestAudioFocus(mOnAudioFocusChangeListener, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN);
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            mHasAudioFocus = true;
        } else {
            pause();
        }
    }

    private void abandonAudioFocus() {
        mHasAudioFocus = false;
        mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
    }

    void updatePlaybackRow() {
        mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
    }

    /**
     * Creates a ListRow for related videos.
     */
    private void addOtherRows() {
        mVideoCursorAdapter = new CursorObjectAdapter(new CardPresenter());
        mVideoCursorAdapter.setMapper(new VideoCursorMapper());

        Bundle args = new Bundle();
        args.putString(VideoContract.VideoEntry.COLUMN_CATEGORY, mSelectedVideo.category);
        args.putString(VideoContract.VideoEntry.COLUMN_DESC, mSelectedVideo.description);
        getLoaderManager().initLoader(RECOMMENDED_VIDEOS_LOADER, args, this);

        HeaderItem header = new HeaderItem(getString(R.string.related_movies));
        mRowsAdapter.add(new ListRow(header, mVideoCursorAdapter));

    }

    private Row createRows(int i) {
        String json = Utils.inputStreamToString(getResources().openRawResource(R.raw.cards_example));
        CardRow[] rows = new Gson().fromJson(json, CardRow[].class);
        //for (CardRow row : rows) {
        //}
        return createCardRow(rows[rows.length - (i + 1)]);
    }

    private Row createCardRow(final CardRow cardRow) {
        switch (cardRow.getType()) {
        case CardRow.TYPE_SECTION_HEADER:
            return new SectionRow(new HeaderItem(cardRow.getTitle()));
        case CardRow.TYPE_DIVIDER:
            return new DividerRow();
        case CardRow.TYPE_DEFAULT:
        default:
            // Build main row using the ImageCardViewPresenter.
            PresenterSelector presenterSelector = new CardPresenterSelector(getActivity());
            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenterSelector);
            for (Card card : cardRow.getCards()) {
                listRowAdapter.add(card);
            }

            return new CardListRow(new HeaderItem(cardRow.getTitle()), listRowAdapter, cardRow);

        }
    }

    private VideoPlayer.RendererBuilder getRendererBuilder() {
        String userAgent = Util.getUserAgent(getActivity(), "ExoVideoPlayer");
        Uri contentUri = Uri.parse(mSelectedVideo.videoUrl);
        int contentType = Util.inferContentType(contentUri.getLastPathSegment());

        switch (contentType) {
        case Util.TYPE_OTHER: {
            return new ExtractorRendererBuilder(getActivity(), userAgent, contentUri);
        }
        default: {
            throw new IllegalStateException("Unsupported type: " + contentType);
        }
        }
    }

    private void preparePlayer() {
        if (mPlayer == null) {
            mPlayer = new VideoPlayer(getRendererBuilder());
            mPlayer.addListener(this);
            mPlayer.seekTo(0L);
            mPlayer.prepare();
        } else {
            mPlayer.stop();
            mPlayer.seekTo(0L);
            mPlayer.setRendererBuilder(getRendererBuilder());
            mPlayer.prepare();
        }
        mPlayer.setPlayWhenReady(true);

        requestAudioFocus();
    }

    private void releasePlayer() {
        if (mPlayer != null) {
            mPlayer.release();
            mPlayer = null;
        }
        abandonAudioFocus();
    }

    @Override
    public void onStateChanged(boolean playWhenReady, int playbackState) {
        switch (playbackState) {
        case ExoPlayer.STATE_BUFFERING:
            // Do nothing.
            break;
        case ExoPlayer.STATE_ENDED:
            mIsMetadataSet = false;
            mMediaController.getTransportControls().skipToNext();
            break;
        case ExoPlayer.STATE_IDLE:
            // Do nothing.
            break;
        case ExoPlayer.STATE_PREPARING:
            mIsMetadataSet = false;
            break;
        case ExoPlayer.STATE_READY:
            // Duration is set here.
            if (!mIsMetadataSet) {
                updateMetadata(mSelectedVideo);
                mIsMetadataSet = true;
            }
            break;
        default:
            // Do nothing.
            break;
        }
    }

    @Override
    public void onError(Exception e) {
        Log.e(TAG, "An error occurred: " + e);
    }

    @Override
    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
            float pixelWidthHeightRatio) {
        // Do nothing.
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
        if (mPlayer != null) {
            mPlayer.setSurface(new Surface(surfaceTexture));
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // Do nothing.
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        if (mPlayer != null) {
            mPlayer.blockingClearSurface();
        }
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Do nothing.
    }

    private int getPlaybackState() {
        Activity activity = getActivity();

        if (activity != null) {
            PlaybackState state = activity.getMediaController().getPlaybackState();
            if (state != null) {
                return state.getState();
            } else {
                return PlaybackState.STATE_NONE;
            }
        }
        return PlaybackState.STATE_NONE;
    }

    private void setPlaybackState(int state) {
        long currPosition = getCurrentPosition();

        PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
                .setActions(getAvailableActions(state));
        stateBuilder.setState(state, currPosition, 1.0f);
        mSession.setPlaybackState(stateBuilder.build());
    }

    private void updateMetadata(final Video video) {
        final MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();

        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, video.id + "");
        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, video.title);
        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, video.studio);
        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, video.description);

        long duration = Utils.getDuration(video.videoUrl);
        metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration);

        // And at minimum the title and artist for legacy support
        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, video.title);
        metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, video.studio);

        Resources res = getResources();
        int cardWidth = res.getDimensionPixelSize(R.dimen.playback_overlay_width);
        int cardHeight = res.getDimensionPixelSize(R.dimen.playback_overlay_height);

        Glide.with(this).load(Uri.parse(video.cardImageUrl)).asBitmap().centerCrop()
                .into(new SimpleTarget<Bitmap>(cardWidth, cardHeight) {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap);
                        mSession.setMetadata(metadataBuilder.build());
                    }
                });
    }

    private void playVideo(Video video, Bundle extras) {
        updateSelectedVideo(video);
        preparePlayer();
        setPlaybackState(PlaybackState.STATE_PAUSED);
        if (extras.getBoolean(AUTO_PLAY)) {
            play();
        } else {
            pause();
        }
    }

    private void startPlaying() {
        // Prepare the player and start playing the selected video
        playVideo(mSelectedVideo, mAutoPlayExtras);

        // Start loading videos for the queue
        Bundle args = new Bundle();
        args.putString(VideoContract.VideoEntry.COLUMN_CATEGORY, mSelectedVideo.category);
        getLoaderManager().initLoader(QUEUE_VIDEOS_LOADER, args, mCallbacks);
    }

    private final class ItemViewClickedListener implements OnItemViewClickedListener {
        @Override
        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
                RowPresenter.ViewHolder rowViewHolder, Row row) {

            if (item instanceof Video) {
                Video video = (Video) item;
                if (video.videoUrl.contains(".html") || video.videoUrl.contains(".php")) {
                    getActivity().setContentView(R.layout.activity_playback_webview);
                } else {
                    Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
                    intent.putExtra(VideoDetailsActivity.VIDEO, video);

                    Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
                            ((ImageCardView) itemViewHolder.view).getMainImageView(),
                            VideoDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
                    getActivity().startActivity(intent, bundle);
                }

            } else if (item instanceof Card) {
                String key = ((Card) item).getLocalImageResourceName().toString();

                if (key.equals("ic_language_white_48dp")) {
                    currentKeyboard++;
                    if (currentKeyboard % 3 == 0) {
                        if (!kauto.IsKoreanMode())
                            kauto.ToggleMode();
                    } else {
                        if (kauto.IsKoreanMode())
                            kauto.ToggleMode();
                    }

                    mRowsAdapter.replace(0, createRows(currentKeyboard % 3));
                    mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
                } else if (key.equals("ic_clear_white_48dp")) {
                    if (mComposing.length() > 0) {
                        mComposing.setLength(0);
                    }
                    kauto.FinishAutomataWithoutInput();
                    loadQuery();
                    mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
                } else if (key.equals("ic_space_bar_white_48dp")) {
                    handleCharacter(' ');
                    loadQuery();
                    mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
                } else if (key.equals("ic_backspace_white_48dp")) {
                    handleBackspace();
                    loadQuery();
                    mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
                } else {

                    handleCharacter(key2char(key));
                    //mQuery = concatOldNew(mQuery, key);
                    loadQuery();
                    mRowsAdapter.notifyArrayItemRangeChanged(1, 1);
                }

            } else {
                Toast.makeText(getActivity(), item.getClass().getName().toString(), Toast.LENGTH_LONG).show();
            }
        }

    }

    private char key2char(String key) {
        //String kor = "???????";
        String kor = "????";
        String eng = "abcdefghijklmnopqrstuvwxyzQWERTOP";

        return kor.contains(key) ? eng.charAt(kor.indexOf(key)) : key.charAt(0);
    }

    private void loadQuery() {

        mQuery = mComposing.toString();
        loadQuery(mQuery);
    }

    private void loadQuery(String query) {

        getLoaderManager().initLoader(mSpecificVideoLoaderId++, null, this);
        Cursor cursor = mVideoCursorAdapter.getCursor();

        HeaderItem header = new HeaderItem(getString(R.string.search_results, query));

        mRowsAdapter.replace(1, new ListRow(header, mVideoCursorAdapter));

        /*
        Cursor cursor = mVideoCursorAdapter.getCursor();
        int titleRes;
        if (cursor != null && cursor.moveToFirst()) {
        mResultsFound = true;
        titleRes = R.string.search_results;
        } else {
        mResultsFound = false;
        titleRes = R.string.no_search_results;
        }
        mVideoCursorAdapter.changeCursor(cursor);
        HeaderItem header = new HeaderItem(getString(titleRes, mQuery));
        ListRow row = new ListRow(header, mVideoCursorAdapter);
        mRowsAdapter.replace(0, row);
         */
    }

    // An event was triggered by MediaController.TransportControls and must be handled here.
    // Here we update the media itself to act on the event that was triggered.
    private class MediaSessionCallback extends MediaSessionCompat.Callback {

        @Override
        public void onPlay() {
            play();
        }

        @Override
        // This method should play any media item regardless of the Queue.
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            Bundle args = new Bundle();
            args.putString(VideoContract.VideoEntry._ID, mediaId);
            getLoaderManager().initLoader(mSpecificVideoLoaderId++, args, mCallbacks);
        }

        @Override
        public void onPause() {
            pause();
        }

        @Override
        public void onSkipToNext() {
            // Update the media to skip to the next video.
            Bundle bundle = new Bundle();
            bundle.putBoolean(AUTO_PLAY, true);

            int nextIndex = ++mQueueIndex;
            if (nextIndex < mQueue.size()) {
                MediaSessionCompat.QueueItem item = mQueue.get(nextIndex);
                String mediaId = item.getDescription().getMediaId();
                getActivity().getMediaController().getTransportControls().playFromMediaId(mediaId, bundle);
            } else {
                getActivity().onBackPressed(); // Return to details presenter.
            }
        }

        @Override
        public void onSkipToPrevious() {
            // Update the media to skip to the previous video.
            setPlaybackState(PlaybackState.STATE_SKIPPING_TO_PREVIOUS);

            Bundle bundle = new Bundle();
            bundle.putBoolean(AUTO_PLAY, true);

            int prevIndex = --mQueueIndex;
            if (prevIndex >= 0) {
                MediaSessionCompat.QueueItem item = mQueue.get(prevIndex);
                String mediaId = item.getDescription().getMediaId();

                getActivity().getMediaController().getTransportControls().playFromMediaId(mediaId, bundle);
            } else {
                getActivity().onBackPressed(); // Return to details presenter.
            }
        }

        @Override
        public void onFastForward() {
            if (mPlayer.getDuration() != ExoPlayer.UNKNOWN_TIME) {
                // Fast forward 10 seconds.
                int prevState = getPlaybackState();
                setPlaybackState(PlaybackState.STATE_FAST_FORWARDING);
                setPosition(mPlayer.getCurrentPosition() + (10 * 1000));
                setPlaybackState(prevState);
            }
        }

        @Override
        public void onRewind() {
            // Rewind 10 seconds.
            int prevState = getPlaybackState();
            setPlaybackState(PlaybackState.STATE_REWINDING);
            setPosition(mPlayer.getCurrentPosition() - (10 * 1000));
            setPlaybackState(prevState);
        }

        @Override
        public void onSeekTo(long position) {
            setPosition(position);
        }
    }

    private boolean isAlphabet(int code) {
        return Character.isLetter(code);
    }

    private void handleCharacter(int primaryCode) {
        int keyState = InputTables.KEYSTATE_NONE;

        if (isAlphabet(primaryCode)) {
            int ret = kauto.DoAutomata((char) primaryCode, keyState);
            if (ret < 0) {
                //Log.v(TAG,"handleCharacter() - DoAutomata() call failed. primaryCode = " + primaryCode + " keyStete = " + keyState);
                if (kauto.IsKoreanMode())
                    kauto.ToggleMode();
            } else {
                // debug block..
                //Log.v(TAG, "handleCharacter - After calling DoAutomata()");
                //Log.v(TAG, "   KoreanMode = [" + (kauto.IsKoreanMode()? "true" : "false") + "]");
                //Log.v(TAG, "   CompleteString = [" + kauto.GetCompleteString() + "]");
                //Log.v(TAG, "   CompositionString = [" + kauto.GetCompositionString() + "]");
                //Log.v(TAG, "   State = [" + kauto.GetState() + "]");
                //Log.v(TAG, "   ret = [" + ret + "]");

                if ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) != 0) {

                    if (mComposing.length() > 0)
                        mComposing.replace(mComposing.length() - 1, mComposing.length(), kauto.GetCompleteString());
                    else
                        mComposing.append(kauto.GetCompleteString());

                    if (mComposing.length() > 0) {
                        //getCurrentInputConnection().setComposingText(mComposing, 1);

                    }
                }
                if ((ret & KoreanAutomata.ACTION_UPDATE_COMPOSITIONSTR) != 0) {
                    if ((mComposing.length() > 0) && ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) == 0)
                            && ((ret & KoreanAutomata.ACTION_APPEND) == 0))
                        mComposing.replace(mComposing.length() - 1, mComposing.length(),
                                kauto.GetCompositionString());
                    else
                        mComposing.append(kauto.GetCompositionString());
                    //getCurrentInputConnection().setComposingText(mComposing, 1);
                }
            }
            if ((ret & KoreanAutomata.ACTION_USE_INPUT_AS_RESULT) != 0) {

                mComposing.append((char) primaryCode);
                //getCurrentInputConnection().setComposingText(mComposing, 1);
            }
            //updateShiftKeyState(getCurrentInputEditorInfo());

        } else {
            if (kauto.IsKoreanMode()) {
                if (mComposing.length() > 0)
                    mComposing.replace(mComposing.length() - 1, mComposing.length(), kauto.GetCompleteString());
                else
                    mComposing.append(kauto.GetCompleteString());

                if ((mComposing.length() > 0))
                    mComposing.replace(mComposing.length() - 1, mComposing.length(), kauto.GetCompositionString());
                else
                    mComposing.append(kauto.GetCompositionString());

                kauto.FinishAutomataWithoutInput();
            }

            mComposing.append((char) primaryCode);
        }
    }

    private void handleBackspace() {

        if (kauto.IsKoreanMode()) {
            int ret = kauto.DoBackSpace();
            if (ret == KoreanAutomata.ACTION_ERROR) {
                // Log.v(TAG, "handleBackspace() - calling DoBackSpace() failed.");
                //// Log.v(TAG, "  mCompositionString = [" + kauto.GetCompositionString()+"]");
                // Log.v(TAG, "  mState = " + kauto.GetState());
                //updateShiftKeyState(getCurrentInputEditorInfo());
                return;
            }
            if ((ret & KoreanAutomata.ACTION_UPDATE_COMPOSITIONSTR) != 0) {
                if (kauto.GetCompositionString() != "") {

                    if (mComposing.length() > 0) {
                        mComposing.replace(mComposing.length() - 1, mComposing.length(),
                                kauto.GetCompositionString());
                        //getCurrentInputConnection().setComposingText(mComposing, 1);
                    }

                    //updateShiftKeyState(getCurrentInputEditorInfo());
                    return;
                }

            }

        }
        final int length = mComposing.length();
        if (length > 1) {
            mComposing.delete(length - 1, length);
            //getCurrentInputConnection().setComposingText(mComposing, 1);

        } else if (length > 0) {
            mComposing.setLength(0);
            //getCurrentInputConnection().commitText("", 0);

        } else {
            //keyDownUp(KeyEvent.KEYCODE_DEL);
        }
        //updateShiftKeyState(getCurrentInputEditorInfo());
    }

}