chromium/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsIntentUtils.java

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

package org.chromium.chromecast.shell;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.content_public.browser.WebContents;

/**
 * Utils for creating and handling intents used by {@link CastWebContentsComponent} and
 * classes communicate with it.
 */
public class CastWebContentsIntentUtils {
    private static final String TAG = "CastWebUtil";

    static final String ACTION_DATA_SCHEME = "cast";
    static final String ACTION_DATA_AUTHORITY = "webcontents";

    private static final boolean DEBUG = true;

    /**
     * Action type of intent from Android to cast app to notify the stop event of
     * CastWebContentsActivity.
     */
    static final String ACTION_ACTIVITY_STOPPED =
            "com.google.android.apps.castshell.intent.action.ACTIVITY_STOPPED";

    /**
     * Action type of intent from Android to cast app to notify the visibility change
     * of cast app in an Android app.
     */
    public static final String ACTION_ON_VISIBILITY_CHANGE =
            "com.google.android.apps.castshell.intent.action.ON_VISIBILITY_CHANGE";

    /**
     * Action type of intent from CastWebContentsComponent to notify CastWebContentsActivity that
     * touch should be enabled.
     */
    public static final String ACTION_ENABLE_TOUCH_INPUT =
            "com.google.android.apps.castshell.intent.action.ENABLE_TOUCH_INPUT";

    /**
     * Action to notify CastWebContentsActivity whether PiP is allowed.
     */
    public static final String ACTION_ALLOW_PICTURE_IN_PICTURE =
            "com.google.android.apps.castshell.intent.action.ALLOW_PICTURE_IN_PICTURE";

    /**
     * Action to notify CastWebContentsActivity whether or not media is playing.
     */
    public static final String ACTION_MEDIA_PLAYING =
            "com.google.android.apps.castshell.intent.action.MEDIA_PLAYING";

    /**
     * Action to request that ACTION_MEDIA_PLAYING status is broadcasted.
     */
    public static final String ACTION_REQUEST_MEDIA_PLAYING_STATUS =
            "com.google.android.apps.castshell.intent.action.REQUEST_MEDIA_PLAYING";

    /** Key of extra value in an intent, the value is a URI of cast://webcontents/<instanceId> */
    static final String INTENT_EXTRA_URI = "content_uri";

    /** Key of extra value of the intent to start a web content, value is app ID of cast app */
    static final String INTENT_EXTRA_APP_ID = "content_app_id";

    /** Key of extra value of the intent to start a web content, value is session ID of cast app */
    static final String INTENT_EXTRA_SESSION_ID = "content_session_id";

    /** Key of extra value of the intent to start a web content, value is true if cast app supports
     *  touch input.
     */
    static final String INTENT_EXTRA_TOUCH_INPUT_ENABLED =
            "com.google.android.apps.castshell.intent.extra.ENABLE_TOUCH";

    /**
     *  Key of extra indicating whether PiP is allowed.
     */
    static final String INTENT_EXTRA_ALLOW_PICTURE_IN_PICTURE =
            "com.google.android.apps.castshell.intent.extra.ALLOW_PICTURE_IN_PICTURE";

    /**
     * Key of extra indicating whether media is playing.
     */
    static final String INTENT_EXTRA_MEDIA_PLAYING =
            "com.google.android.apps.castshell.intent.extra.MEDIA_PLAYING";

    /** Key of extra value of the intent to start a web content, value is true is if cast app is
     *  a remote control app.
     */
    static final String INTENT_EXTRA_SHOULD_REQUEST_AUDIO_FOCUS =
            "com.google.android.apps.castshell.intent.extra.SHOULD_REQUEST_AUDIO_FOCUS";

    /** Key for extra value for intent to start web contents. true if the app should turn on the
     * display. */
    static final String INTENT_EXTRA_TURN_ON_SCREEN =
            "com.google.android.apps.castshell.intent.extra.TURN_ON_SCREEN";

    /**
     * Key for extra value fot intent to start web contents. true if the app should keep the
     * screen on.
     */
    static final String INTENT_EXTRA_KEEP_SCREEN_ON =
            "com.google.android.apps.castshell.intent.extra.KEEP_SCREEN_ON";

    /**
     * Key of extra value of the intent ACTION_REQUEST_VISIBILITY, value is visibility priority
     * (int).
     */
    static final String INTENT_EXTRA_VISIBILITY_PRIORITY =
            "com.google.android.apps.castshell.intent.extra.content_visibility_priority";

    /** Key of extra value of the intent to start a web content, value is true is touch is enabled.
     */
    private static final String INTENT_EXTRA_KEY_CODE =
            "com.google.android.apps.castshell.intent.extra.KEY_CODE";

    /**
     * Key of extra value of the intent ACTION_ON_VISIBILITY_CHANGE, value is visibility type
     * (int).
     */
    private static final String INTENT_EXTRA_VISIBILITY_TYPE =
            "com.google.android.apps.castshell.intent.extra.VISIBILITY_TYPE";

    @VisibilityType
    static final int VISIBITY_TYPE_UNKNOWN = VisibilityType.UNKNOWN;
    @VisibilityType
    static final int VISIBITY_TYPE_FULL_SCREEN = VisibilityType.FULL_SCREEN;
    @VisibilityType
    static final int VISIBITY_TYPE_PARTIAL_OUT = VisibilityType.PARTIAL_OUT;
    @VisibilityType
    static final int VISIBITY_TYPE_HIDDEN = VisibilityType.HIDDEN;
    @VisibilityType
    static final int VISIBITY_TYPE_TRANSIENTLY_HIDDEN = VisibilityType.TRANSIENTLY_HIDDEN;

    // CastWebContentsSurfaceHelper -> CastWebContentsComponent.Receiver
    // -> CastContentWindowAndroid
    public static Intent onActivityStopped(String instanceId) {
        Intent intent = new Intent(ACTION_ACTIVITY_STOPPED, getInstanceUri(instanceId));
        return intent;
    }

    // CastWebContentsActivity -> CastWebContentsComponent.Receiver -> CastContentWindowAndroid
    public static Intent onVisibilityChange(String instanceId, @VisibilityType int visibilityType) {
        return onVisibilityChange(getInstanceUri(instanceId), visibilityType);
    }

    private static Intent onVisibilityChange(Uri uri, @VisibilityType int visibilityType) {
        if (DEBUG) Log.d(TAG, "onVisibilityChange with uri:" + uri + " type:" + visibilityType);

        Intent intent = new Intent(ACTION_ON_VISIBILITY_CHANGE, uri);
        intent.putExtra(INTENT_EXTRA_VISIBILITY_TYPE, visibilityType);
        return intent;
    }

    public static Intent requestMediaPlayingStatus(String instanceId) {
        Uri uri = getInstanceUri(instanceId);
        if (DEBUG) Log.d(TAG, "requestMediaPlayingStatus with uri: " + uri);
        return new Intent(ACTION_REQUEST_MEDIA_PLAYING_STATUS, uri);
    }

    // Used by intent of ACTION_ON_VISIBILITY_CHANGE
    @VisibilityType
    public static int getVisibilityType(Intent in) {
        return in.getIntExtra(INTENT_EXTRA_VISIBILITY_TYPE, 0);
    }

    public static boolean isIntentOfActivityStopped(Intent in) {
        return in.getAction().equals(ACTION_ACTIVITY_STOPPED);
    }

    public static boolean isIntentOfVisibilityChange(Intent in) {
        return in.getAction().equals(ACTION_ON_VISIBILITY_CHANGE);
    }

    public static boolean isIntentOfRequestMediaPlayingStatus(Intent in) {
        return in.getAction().equals(ACTION_REQUEST_MEDIA_PLAYING_STATUS);
    }

    // CastWebContentsComponent.Receiver -> CastWebContentsActivity
    public static Intent requestStartCastActivity(Context context, WebContents webContents,
            boolean enableTouch, boolean shouldRequestAudioFocus, boolean turnOnScreen,
            boolean keepScreenOn, String instanceId) {
        WebContentsRegistry.addWebContents(instanceId, webContents);
        Intent intent =
                new Intent(Intent.ACTION_VIEW, null, context, CastWebContentsActivity.class);
        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
        intent.putExtra(INTENT_EXTRA_SESSION_ID, instanceId);
        intent.putExtra(INTENT_EXTRA_TOUCH_INPUT_ENABLED, enableTouch);
        intent.putExtra(INTENT_EXTRA_TURN_ON_SCREEN, turnOnScreen);
        intent.putExtra(INTENT_EXTRA_KEEP_SCREEN_ON, keepScreenOn);
        intent.putExtra(INTENT_EXTRA_SHOULD_REQUEST_AUDIO_FOCUS, shouldRequestAudioFocus);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
        return intent;
    }

    // CastWebContentsComponent.Receiver -> CastWebContentsService
    public static Intent requestStartCastService(
            Context context, WebContents webContents, String instanceId) {
        WebContentsRegistry.addWebContents(instanceId, webContents);
        Intent intent = new Intent(Intent.ACTION_VIEW, getInstanceUri(instanceId), context,
                CastWebContentsService.class);
        intent.putExtra(INTENT_EXTRA_SESSION_ID, instanceId);
        return intent;
    }

    // CastWebContentsComponent.Delegate -> CastWebContentsSurfaceHelper
    public static Intent requestStopWebContents(String instanceId) {
        WebContentsRegistry.removeWebContents(instanceId);
        Intent intent = new Intent(CastIntents.ACTION_STOP_WEB_CONTENT);
        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
        return intent;
    }

    // Used by ACTION_VIEW
    public static String getSessionId(Bundle bundle) {
        return bundle.getString(INTENT_EXTRA_SESSION_ID);
    }

    // Used by ACTION_VIEW
    public static String getSessionId(Intent in) {
        return getSessionId(in.getExtras());
    }

    // Used by ACTION_VIEW
    public static WebContents getWebContents(Bundle bundle) {
        String sessionId = bundle.getString(INTENT_EXTRA_SESSION_ID);
        return WebContentsRegistry.getWebContents(sessionId);
    }

    // Used by ACTION_VIEW
    public static WebContents getWebContents(Intent in) {
        return getWebContents(in.getExtras());
    }

    // Used by ACTION_VIEW
    public static String getUriString(Bundle bundle) {
        return bundle.getString(INTENT_EXTRA_URI);
    }

    // Used by ACTION_VIEW
    public static String getUriString(Intent in) {
        return getUriString(in.getExtras());
    }

    // Used by ACTION_VIEW
    public static boolean isTouchable(Bundle bundle) {
        return bundle.getBoolean(INTENT_EXTRA_TOUCH_INPUT_ENABLED);
    }

    // Used by ACTION_VIEW
    public static boolean isTouchable(Intent in) {
        return isTouchable(in.getExtras());
    }

    // Used by ACTION_ALLOW_PICTURE_IN_PICTURE
    public static boolean isPictureInPictureAllowed(Intent in) {
        return in.getExtras().getBoolean(INTENT_EXTRA_ALLOW_PICTURE_IN_PICTURE);
    }

    // Used by ACTION_MEDIA_PLAYING
    public static boolean isMediaPlaying(Intent in) {
        return in.getExtras().getBoolean(INTENT_EXTRA_MEDIA_PLAYING);
    }

    // Used by ACTION_VIEW
    public static boolean shouldRequestAudioFocus(Bundle bundle) {
        return bundle.getBoolean(INTENT_EXTRA_SHOULD_REQUEST_AUDIO_FOCUS);
    }

    // Used by ACTION_VIEW
    public static boolean shouldRequestAudioFocus(Intent in) {
        return shouldRequestAudioFocus(in.getExtras());
    }

    // Used by ACTION_VIEW
    public static boolean shouldTurnOnScreen(Intent intent) {
        return intent.getBooleanExtra(INTENT_EXTRA_TURN_ON_SCREEN, true);
    }

    // Used by ACTION_VIEW and ACTION_SHOW_WEB_CONTENT
    public static boolean shouldKeepScreenOn(Intent intent) {
        return intent.getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false);
    }

    // CastWebContentsComponent -> CastWebContentsSurfaceHelper
    public static Intent enableTouchInput(String instanceId, boolean enabled) {
        Intent intent = new Intent(ACTION_ENABLE_TOUCH_INPUT);
        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
        intent.putExtra(INTENT_EXTRA_TOUCH_INPUT_ENABLED, enabled);
        return intent;
    }

    // CastWebContentsComponent -> CastWebContentsActivity
    public static Intent allowPictureInPicture(String instanceId, boolean allowPictureInPicture) {
        Intent intent = new Intent(ACTION_ALLOW_PICTURE_IN_PICTURE);
        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
        intent.putExtra(INTENT_EXTRA_ALLOW_PICTURE_IN_PICTURE, allowPictureInPicture);
        return intent;
    }

    public static Intent mediaPlaying(String instanceId, boolean mediaPlaying) {
        Intent intent = new Intent(ACTION_MEDIA_PLAYING);
        intent.putExtra(INTENT_EXTRA_URI, getInstanceUri(instanceId).toString());
        intent.putExtra(INTENT_EXTRA_MEDIA_PLAYING, mediaPlaying);
        return intent;
    }

    // CastWebContentsSurfaceHelper -> CastWebContentsActivity
    public static Intent onWebContentStopped(Uri uri) {
        Intent intent = new Intent(CastIntents.ACTION_ON_WEB_CONTENT_STOPPED);
        intent.putExtra(INTENT_EXTRA_URI, uri.toString());
        return intent;
    }

    public static Uri getInstanceUri(String instanceId) {
        Uri instanceUri = new Uri.Builder()
                                  .scheme(ACTION_DATA_SCHEME)
                                  .authority(ACTION_DATA_AUTHORITY)
                                  .path(instanceId)
                                  .build();
        return instanceUri;
    }

    static LocalBroadcastManager getLocalBroadcastManager() {
        return LocalBroadcastManager.getInstance(ContextUtils.getApplicationContext());
    }
}