org.chromium.chrome.browser.media.remote.ExpandedControllerActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.chromium.chrome.browser.media.remote.ExpandedControllerActivity.java

Source

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.media.remote;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.support.v4.media.TransportMediator;
import android.support.v4.media.TransportPerformer;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.android.gms.cast.CastMediaControlIntent;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.chrome.browser.metrics.MediaNotificationUma;
import org.chromium.third_party.android.media.MediaController;

/**
 * The activity that's opened by clicking the video flinging (casting) notification.
 *
 * TODO(aberent): Refactor to merge some common logic with {@link CastNotificationControl}.
 */
public class ExpandedControllerActivity extends FragmentActivity implements MediaRouteController.UiListener {
    private static final int PROGRESS_UPDATE_PERIOD_IN_MS = 1000;
    // The alpha value for the poster/placeholder image, an integer between 0 and 256 (opaque).
    private static final int POSTER_IMAGE_ALPHA = 200;

    private Handler mHandler;
    // We don't use the standard android.media.MediaController, but a custom one.
    // See the class itself for details.
    private MediaController mMediaController;
    private FullscreenMediaRouteButton mMediaRouteButton;
    private MediaRouteController mMediaRouteController;
    private RemoteVideoInfo mVideoInfo;
    private String mScreenName;
    private TransportMediator mTransportMediator;

    /**
     * Handle actions from on-screen media controls.
     */
    private TransportPerformer mTransportPerformer = new TransportPerformer() {
        @Override
        public void onStart() {
            if (mMediaRouteController == null)
                return;
            mMediaRouteController.resume();
            RecordCastAction.recordFullscreenControlsAction(RecordCastAction.FULLSCREEN_CONTROLS_RESUME,
                    mMediaRouteController.getMediaStateListener() != null);
        }

        @Override
        public void onStop() {
            if (mMediaRouteController == null)
                return;
            onPause();
            mMediaRouteController.release();
        }

        @Override
        public void onPause() {
            if (mMediaRouteController == null)
                return;
            mMediaRouteController.pause();
            RecordCastAction.recordFullscreenControlsAction(RecordCastAction.FULLSCREEN_CONTROLS_PAUSE,
                    mMediaRouteController.getMediaStateListener() != null);
        }

        @Override
        public long onGetDuration() {
            if (mMediaRouteController == null)
                return 0;
            return mMediaRouteController.getDuration();
        }

        @Override
        public long onGetCurrentPosition() {
            if (mMediaRouteController == null)
                return 0;
            return mMediaRouteController.getPosition();
        }

        @Override
        public void onSeekTo(long pos) {
            if (mMediaRouteController == null)
                return;
            mMediaRouteController.seekTo(pos);
            RecordCastAction.recordFullscreenControlsAction(RecordCastAction.FULLSCREEN_CONTROLS_SEEK,
                    mMediaRouteController.getMediaStateListener() != null);
        }

        @Override
        public boolean onIsPlaying() {
            if (mMediaRouteController == null)
                return false;
            return mMediaRouteController.isPlaying();
        }

        @Override
        public int onGetTransportControlFlags() {
            int flags = TransportMediator.FLAG_KEY_MEDIA_REWIND | TransportMediator.FLAG_KEY_MEDIA_FAST_FORWARD;
            if (mMediaRouteController != null && mMediaRouteController.isPlaying()) {
                flags |= TransportMediator.FLAG_KEY_MEDIA_PAUSE;
            } else {
                flags |= TransportMediator.FLAG_KEY_MEDIA_PLAY;
            }
            return flags;
        }
    };

    private Runnable mProgressUpdater = new Runnable() {
        @Override
        public void run() {
            if (mMediaRouteController.isPlaying()) {
                mMediaController.updateProgress();
                mHandler.postDelayed(this, PROGRESS_UPDATE_PERIOD_IN_MS);
            } else {
                mHandler.removeCallbacks(this);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MediaNotificationUma.recordClickSource(getIntent());

        mMediaRouteController = RemoteMediaPlayerController.instance().getCurrentlyPlayingMediaRouteController();

        if (mMediaRouteController == null || mMediaRouteController.routeIsDefaultRoute()) {
            // We don't want to do anything for the default (local) route
            finish();
            return;
        }

        // Make the activity full screen.
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // requestWindowFeature must be called before adding content.
        setContentView(R.layout.expanded_cast_controller);
        mHandler = new Handler();

        ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content);
        rootView.setBackgroundColor(Color.BLACK);

        mMediaRouteController.addUiListener(this);

        // Create transport controller to control video, giving the callback
        // interface to receive actions from.
        mTransportMediator = new TransportMediator(this, mTransportPerformer);

        // Create and initialize the media control UI.
        mMediaController = (MediaController) findViewById(R.id.cast_media_controller);
        mMediaController.setMediaPlayer(mTransportMediator);

        View button = getLayoutInflater().inflate(R.layout.cast_controller_media_route_button, rootView, false);

        if (button instanceof FullscreenMediaRouteButton) {
            mMediaRouteButton = (FullscreenMediaRouteButton) button;
            rootView.addView(mMediaRouteButton);
            mMediaRouteButton.bringToFront();
            mMediaRouteButton.initialize(mMediaRouteController);
        } else {
            mMediaRouteButton = null;
        }

        // Initialize the video info.
        setVideoInfo(new RemoteVideoInfo(null, 0, RemoteVideoInfo.PlayerState.STOPPED, 0, null));

        mMediaController.refresh();

        scheduleProgressUpdate();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mVideoInfo.state == PlayerState.FINISHED)
            finish();
        if (mMediaRouteController == null)
            return;

        // Lifetime of the media element is bound to that of the {@link MediaStateListener}
        // of the {@link MediaRouteController}.
        RecordCastAction.recordFullscreenControlsShown(mMediaRouteController.getMediaStateListener() != null);

        mMediaRouteController.prepareMediaRoute();

        ImageView iv = (ImageView) findViewById(R.id.cast_background_image);
        if (iv == null)
            return;
        Bitmap posterBitmap = mMediaRouteController.getPoster();
        if (posterBitmap != null)
            iv.setImageBitmap(posterBitmap);
        iv.setImageAlpha(POSTER_IMAGE_ALPHA);
    }

    @Override
    protected void onDestroy() {
        cleanup();
        super.onDestroy();
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        int keyCode = event.getKeyCode();
        if ((keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP)
                || mVideoInfo.state == PlayerState.FINISHED) {
            return super.dispatchKeyEvent(event);
        }

        return handleVolumeKeyEvent(mMediaRouteController, event);
    }

    private void cleanup() {
        if (mHandler != null)
            mHandler.removeCallbacks(mProgressUpdater);
        if (mMediaRouteController != null)
            mMediaRouteController.removeUiListener(this);
        mMediaRouteController = null;
        mProgressUpdater = null;
    }

    /**
     * Sets the remote's video information to display.
     */
    private final void setVideoInfo(RemoteVideoInfo videoInfo) {
        if ((mVideoInfo == null) ? (videoInfo == null) : mVideoInfo.equals(videoInfo))
            return;

        mVideoInfo = videoInfo;
        onVideoInfoChanged();
    }

    private void scheduleProgressUpdate() {
        mHandler.removeCallbacks(mProgressUpdater);
        if (mMediaRouteController.isPlaying()) {
            mHandler.post(mProgressUpdater);
        }
    }

    /**
     * Sets the name to display for the device.
     */
    private void setScreenName(String screenName) {
        if (TextUtils.equals(mScreenName, screenName))
            return;

        mScreenName = screenName;
        onScreenNameChanged();
    }

    private void onVideoInfoChanged() {
        updateUi();
    }

    private void onScreenNameChanged() {
        updateUi();
    }

    private void updateUi() {
        if (mMediaController == null || mMediaRouteController == null)
            return;

        String deviceName = mMediaRouteController.getRouteName();
        String castText = "";
        if (deviceName != null) {
            castText = getResources().getString(R.string.cast_casting_video, deviceName);
        }
        TextView castTextView = (TextView) findViewById(R.id.cast_screen_title);
        castTextView.setText(castText);

        mMediaController.refresh();
    }

    @Override
    public void onRouteSelected(String name, MediaRouteController mediaRouteController) {
        setScreenName(name);
    }

    @Override
    public void onRouteUnselected(MediaRouteController mediaRouteController) {
        finish();
    }

    @Override
    public void onPrepared(MediaRouteController mediaRouteController) {
        // No implementation.
    }

    @Override
    public void onError(int error, String message) {
        if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED)
            finish();
    }

    @Override
    public void onPlaybackStateChanged(PlayerState newState) {
        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
        videoInfo.state = newState;
        setVideoInfo(videoInfo);

        scheduleProgressUpdate();

        if (newState == PlayerState.FINISHED || newState == PlayerState.INVALIDATED) {
            // If we are switching to a finished state, stop the notifications.
            finish();
        }
    }

    @Override
    public void onDurationUpdated(long durationMillis) {
        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
        videoInfo.durationMillis = durationMillis;
        setVideoInfo(videoInfo);
    }

    @Override
    public void onPositionChanged(long positionMillis) {
        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
        videoInfo.currentTimeMillis = positionMillis;
        setVideoInfo(videoInfo);
    }

    @Override
    public void onTitleChanged(String title) {
        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
        videoInfo.title = title;
        setVideoInfo(videoInfo);
    }

    /**
     * Modify remote volume by handling volume keys.
     *
     * @param controller The remote controller through which the volume will be modified.
     * @param event The key event. Its keycode needs to be either {@code KEYCODE_VOLUME_DOWN} or
     *              {@code KEYCODE_VOLUME_UP} otherwise this method will return false.
     * @return True if the event is handled.
     */
    private boolean handleVolumeKeyEvent(MediaRouteController controller, KeyEvent event) {
        if (!controller.isBeingCast())
            return false;

        int action = event.getAction();
        int keyCode = event.getKeyCode();
        // Intercept the volume keys to affect only remote volume.
        switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            if (action == KeyEvent.ACTION_DOWN)
                controller.setRemoteVolume(-1);
            return true;
        case KeyEvent.KEYCODE_VOLUME_UP:
            if (action == KeyEvent.ACTION_DOWN)
                controller.setRemoteVolume(1);
            return true;
        default:
            return false;
        }
    }

    /**
     * Launches the ExpandedControllerActivity as a new task.
     *
     * @param context the Context to start this activity within.
     */
    public static void startActivity(Context context) {
        if (context == null)
            return;

        Intent intent = new Intent(context, ExpandedControllerActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}