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

// Copyright 2016 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.hardware.display.DisplayManager;
import android.view.Display;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

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

/**
 * The Java component of CastContentWindowAndroid. This class is responsible for
 * starting, stopping and monitoring CastWebContentsActivity.
 * <p>
 * See chromecast/browser/cast_content_window_android.* for the native half.
 */
@JNINamespace("chromecast")
public class CastContentWindowAndroid implements CastWebContentsComponent.OnComponentClosedHandler,
                                                 CastWebContentsComponent.SurfaceEventHandler {
    private static final String TAG = "CastContentWindow";
    private static final boolean DEBUG = true;

    // Note: CastContentWindowAndroid may outlive the native object. The native
    // ref should be checked that it is not zero before it is used.
    private long mNativeCastContentWindowAndroid;
    private Context mContext;
    private CastWebContentsComponent mComponent;

    private boolean mScreenAccess;
    private CastWebContentsComponent.StartParams mStartParams;

    private static int sInstanceId = 1;

    @SuppressWarnings("unused")
    @CalledByNative
    private static CastContentWindowAndroid create(long nativeCastContentWindowAndroid,
            boolean enableTouchInput, boolean turnOnScreen, boolean keepScreenOn, String sessionId,
            int displayId) {
        return new CastContentWindowAndroid(nativeCastContentWindowAndroid,
                getContextWithDisplay(displayId), enableTouchInput, turnOnScreen, keepScreenOn,
                sessionId);
    }

    private static Context getContextWithDisplay(int displayId) {
        Context context = ContextUtils.getApplicationContext();
        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
        Display display = displayManager.getDisplay(displayId);
        if (display != null) {
            return context.createDisplayContext(display);
        }
        Log.i(TAG,
                "Display with the given cast display id is not available, "
                        + "use the default display to create the web view.");
        return context;
    }

    private CastContentWindowAndroid(long nativeCastContentWindowAndroid, final Context context,
            boolean enableTouchInput, boolean turnOnScreen, boolean keepScreenOn,
            String sessionId) {
        mNativeCastContentWindowAndroid = nativeCastContentWindowAndroid;
        mContext = context;
        Log.i(TAG,
                "Creating new CastContentWindowAndroid(No. " + sInstanceId++
                        + ") Seesion ID: " + sessionId);
        mComponent = new CastWebContentsComponent(
                sessionId, this, this, enableTouchInput, turnOnScreen, keepScreenOn);
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void createWindowForWebContents(
            WebContents webContents, String appId, boolean shouldRequestAudioFocus) {
        if (DEBUG) Log.d(TAG, "createWindowForWebContents");
        mStartParams = new CastWebContentsComponent.StartParams(
                mContext, webContents, appId, shouldRequestAudioFocus);
        maybeStartComponent();
    }

    private void maybeStartComponent() {
        if (mStartParams == null || !mScreenAccess) return;

        Log.d(TAG, "mComponent.start()");
        mComponent.start(mStartParams, !mScreenAccess /* isHeadless */);
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void grantScreenAccess() {
        if (DEBUG) Log.d(TAG, "grantScreenAccess");
        mScreenAccess = true;
        maybeStartComponent();
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void revokeScreenAccess() {
        if (DEBUG) Log.d(TAG, "revokeScreenAccess");
        mComponent.stop(mContext);
        mScreenAccess = false;
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void enableTouchInput(boolean enabled) {
        if (DEBUG) Log.d(TAG, "enableTouchInput");
        mComponent.enableTouchInput(enabled);
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void setAllowPictureInPicture(boolean allowPictureInPicture) {
        mComponent.setAllowPictureInPicture(allowPictureInPicture);
    }

    @CalledByNative
    private void setMediaPlaying(boolean mediaPlaying) {
        mComponent.setMediaPlaying(mediaPlaying);
    }

    @SuppressWarnings("unused")
    @CalledByNative
    private void onNativeDestroyed() {
        assert mNativeCastContentWindowAndroid != 0;
        mNativeCastContentWindowAndroid = 0;

        // Note: there is a potential race condition when this function is called after
        // createWindowForWebContents. If the stop intent is received after the start intent but
        // before onCreate, the activity won't shutdown.
        // TODO(derekjchow): Add a unittest to check this behaviour. Also consider using
        // Instrumentation.startActivitySync to guarentee onCreate is run.

        if (DEBUG) Log.d(TAG, "onNativeDestroyed");
        mComponent.stop(mContext);
    }

    @Override
    public void onComponentClosed() {
        if (DEBUG) Log.d(TAG, "onComponentClosed");
        if (mNativeCastContentWindowAndroid != 0) {
            CastContentWindowAndroidJni.get().onActivityStopped(
                    mNativeCastContentWindowAndroid, CastContentWindowAndroid.this);
        }
    }

    @Override
    public void onVisibilityChange(int visibilityType) {
        if (DEBUG) Log.d(TAG, "onVisibilityChange type=" + visibilityType);
        if (mNativeCastContentWindowAndroid != 0) {
            CastContentWindowAndroidJni.get().onVisibilityChange(
                    mNativeCastContentWindowAndroid, CastContentWindowAndroid.this, visibilityType);
        }
    }

    @NativeMethods
    interface Natives {
        void onActivityStopped(
                long nativeCastContentWindowAndroid, CastContentWindowAndroid caller);
        void onVisibilityChange(long nativeCastContentWindowAndroid,
                CastContentWindowAndroid caller, int visibilityType);
    }
}