Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Unified Diff: chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java

Issue 2570623003: [Chromecast] Turn CastContentWindow into an abstract interface. (Closed)
Patch Set: Fix browser test Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..49201cd15c82f6202791c27263ac25d671ed8258
--- /dev/null
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsActivity.java
@@ -0,0 +1,354 @@
+// Copyright 2016 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.chromecast.shell;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content.browser.ActivityContentVideoViewEmbedder;
+import org.chromium.content.browser.ContentVideoViewEmbedder;
+import org.chromium.content.browser.ContentView;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewRenderView;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.ViewAndroidDelegate;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Activity for displaying a WebContents in CastShell.
+ *
+ * Typically, this class is controlled by CastContentWindowAndroid, which will
+ * start a new instance of this activity. If the CastContentWindowAndroid is
+ * destroyed, CastWebContentsActivity should finish(). Similarily, if this
+ * activity is destroyed, CastContentWindowAndroid should be notified by intent.
+ */
+@JNINamespace("chromecast::shell")
+public class CastWebContentsActivity extends Activity {
+ private static final String TAG = "cr_CastWebActivity";
+ private static final boolean DEBUG = true;
+
+ private Handler mHandler;
+ private String mInstanceId;
+ private BroadcastReceiver mWindowDestroyedBroadcastReceiver;
+ private IntentFilter mWindowDestroyedIntentFilter;
+ private FrameLayout mCastWebContentsLayout;
+ private AudioManager mAudioManager;
+ private ContentViewClient mContentViewClient;
+ private ContentViewRenderView mContentViewRenderView;
+ private WindowAndroid mWindow;
+ private ContentViewCore mContentViewCore;
+ private ContentView mContentView;
+
+ private static final int TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS = 300;
+ public static final String ACTION_DATA_SCHEME = "cast";
+ public static final String ACTION_DATA_AUTHORITY = "webcontents";
+
+ public static final String ACTION_EXTRA_WEB_CONTENTS =
+ "com.google.android.apps.castshell.intent.extra.WEB_CONTENTS";
+ public static final String ACTION_EXTRA_KEY_CODE =
+ "com.google.android.apps.castshell.intent.extra.KEY_CODE";
+ public static final String ACTION_KEY_EVENT =
+ "com.google.android.apps.castshell.intent.action.KEY_EVENT";
+ public static final String ACTION_STOP_ACTIVITY =
+ "com.google.android.apps.castshell.intent.action.STOP_ACTIVITY";
+ public static final String ACTION_ACTIVITY_STOPPED =
+ "com.google.android.apps.castshell.intent.action.ACTIVITY_STOPPED";
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ mHandler = new Handler();
+
+ // TODO(derekjchow): Remove this call.
+ if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) {
+ Toast.makeText(this, R.string.browser_process_initialization_failed, Toast.LENGTH_SHORT)
+ .show();
+ finish();
+ }
+
+ // Whenever our app is visible, volume controls should modify the music stream.
+ // For more information read:
+ // http://developer.android.com/training/managing-audio/volume-playback.html
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Set flags to both exit sleep mode when this activity starts and
+ // avoid entering sleep mode while playing media. We cannot distinguish
+ // between video and audio so this applies to both.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ mAudioManager = CastAudioManager.getAudioManager(this);
+
+ setContentView(R.layout.cast_web_contents_activity);
+
+ mWindow = new WindowAndroid(this);
+ mContentViewRenderView = new ContentViewRenderView(this) {
+ @Override
+ protected void onReadyToRender() {
+ setOverlayVideoMode(true);
+ }
+ };
+ mContentViewRenderView.onNativeLibraryLoaded(mWindow);
+ // Setting the background color to black avoids rendering a white splash screen
+ // before the players are loaded. See crbug/307113 for details.
+ mContentViewRenderView.setSurfaceViewBackgroundColor(Color.BLACK);
+
+ mCastWebContentsLayout = (FrameLayout) findViewById(R.id.web_contents_container);
+ mCastWebContentsLayout.addView(mContentViewRenderView,
+ new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+
+ Intent intent = getIntent();
+ handleIntent(intent);
+ }
+
+ protected void handleIntent(Intent intent) {
+ intent.setExtrasClassLoader(WebContents.class.getClassLoader());
+ mInstanceId = intent.getData().getPath();
+
+ final String instanceId = mInstanceId;
+ if (mWindowDestroyedBroadcastReceiver != null) {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(
+ mWindowDestroyedBroadcastReceiver);
+ }
+ mWindowDestroyedBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ detachWebContentsIfAny();
+ maybeFinishLater();
+ }
+ };
+ mWindowDestroyedIntentFilter = new IntentFilter();
+ mWindowDestroyedIntentFilter.addDataScheme(intent.getData().getScheme());
+ mWindowDestroyedIntentFilter.addDataAuthority(intent.getData().getAuthority(), null);
+ mWindowDestroyedIntentFilter.addDataPath(mInstanceId, PatternMatcher.PATTERN_LITERAL);
+ mWindowDestroyedIntentFilter.addAction(ACTION_STOP_ACTIVITY);
+ LocalBroadcastManager.getInstance(this).registerReceiver(
+ mWindowDestroyedBroadcastReceiver, mWindowDestroyedIntentFilter);
+
+ WebContents webContents =
+ (WebContents) intent.getParcelableExtra(ACTION_EXTRA_WEB_CONTENTS);
+ if (webContents == null) {
+ Log.e(TAG, "Received null WebContents in intent.");
+ maybeFinishLater();
+ return;
+ }
+
+ detachWebContentsIfAny();
+ showWebContents(webContents);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onNewIntent");
+
+ // If we're currently finishing this activity, we should start a new activity to
+ // display the new app.
+ if (isFinishing()) {
+ Log.d(TAG, "Activity is finishing, starting new activity.");
+ int flags = intent.getFlags();
+ flags = flags & ~Intent.FLAG_ACTIVITY_SINGLE_TOP;
+ intent.setFlags(flags);
+ startActivity(intent);
+ return;
+ }
+
+ handleIntent(intent);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onStart() {
+ if (DEBUG) Log.d(TAG, "onStart");
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ if (DEBUG) Log.d(TAG, "onStop");
+
+ detachWebContentsIfAny();
+ releaseStreamMuteIfNecessary();
+ super.onStop();
+ }
+
+ @Override
+ protected void onResume() {
+ if (DEBUG) Log.d(TAG, "onResume");
+ super.onResume();
+
+ if (mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Log.e(TAG, "Failed to obtain audio focus");
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (DEBUG) Log.d(TAG, "onPause");
+ super.onPause();
+
+ // Release the audio focus. Note that releasing audio focus does not stop audio playback,
+ // it just notifies the framework that this activity has stopped playing audio.
+ if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Log.e(TAG, "Failed to abandon audio focus");
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
+ int keyCode = event.getKeyCode();
+ int action = event.getAction();
+
+ // Similar condition for all single-click events.
+ if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE
+ || keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {
+ Intent intent = new Intent(ACTION_KEY_EVENT, getInstanceUri());
+ intent.putExtra(ACTION_EXTRA_KEY_CODE, keyCode);
+ LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent);
+ return true;
+ }
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return super.dispatchKeyEvent(event);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ private void releaseStreamMuteIfNecessary() {
+ AudioManager audioManager = CastAudioManager.getAudioManager(this);
+ boolean isMuted = false;
+ try {
+ isMuted = (Boolean) audioManager.getClass()
+ .getMethod("isStreamMute", int.class)
+ .invoke(audioManager, AudioManager.STREAM_MUSIC);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot call AudioManager.isStreamMute().", e);
+ }
+
+ if (isMuted) {
+ // Note: this is a no-op on fixed-volume devices.
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
+ }
+ }
+
+ // Closes this activity if a new WebContents is not being displayed.
+ private void maybeFinishLater() {
+ Log.d(TAG, "maybeFinishLater");
+ final String currentInstanceId = mInstanceId;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (currentInstanceId == mInstanceId) {
+ Log.d(TAG, "Finishing.");
+ finish();
+ }
+ }
+ }, TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS);
+ }
+
+ // Sets webContents to be the currently displayed webContents.
+ private void showWebContents(WebContents webContents) {
+ if (DEBUG) Log.d(TAG, "showWebContents");
+
+ // Set ContentVideoViewEmbedder to allow video playback.
+ nativeSetContentVideoViewEmbedder(webContents, new ActivityContentVideoViewEmbedder(this));
+
+ // TODO(derekjchow): productVersion
+ mContentViewCore = new ContentViewCore(this, "");
+ mContentView = ContentView.createContentView(this, mContentViewCore);
+ mContentViewCore.initialize(ViewAndroidDelegate.createBasicDelegate(mContentView),
+ mContentView, webContents, mWindow);
+ mContentViewClient = new ContentViewClient();
+ mContentViewCore.setContentViewClient(mContentViewClient);
+ // Enable display of current webContents.
+ if (getParent() != null) mContentViewCore.onShow();
+ mCastWebContentsLayout.addView(
+ mContentView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+ mContentView.requestFocus();
+ mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
+ }
+
+ // Remove the currently displayed webContents. no-op if nothing is being displayed.
+ private void detachWebContentsIfAny() {
+ if (DEBUG) Log.d(TAG, "detachWebContentsIfAny");
+ if (mContentView != null) {
+ mCastWebContentsLayout.removeView(mContentView);
+ mContentView = null;
+ mContentViewCore = null;
+
+ // Inform CastContentWindowAndroid we're detaching.
+ Intent intent = new Intent(ACTION_ACTIVITY_STOPPED, getInstanceUri());
+ LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent);
+ }
+ }
+
+ private Uri getInstanceUri() {
+ Uri instanceUri = new Uri.Builder()
+ .scheme(CastWebContentsActivity.ACTION_DATA_SCHEME)
+ .authority(CastWebContentsActivity.ACTION_DATA_AUTHORITY)
+ .path(mInstanceId)
+ .build();
+ return instanceUri;
+ }
+
+ private native void nativeSetContentVideoViewEmbedder(
+ WebContents webContents, ContentVideoViewEmbedder embedder);
+}

Powered by Google App Engine
This is Rietveld 408576698