OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chromecast.shell; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.content.BroadcastReceiver; |
| 9 import android.content.Context; |
| 10 import android.content.Intent; |
| 11 import android.content.IntentFilter; |
| 12 import android.graphics.Color; |
| 13 import android.media.AudioManager; |
| 14 import android.net.Uri; |
| 15 import android.os.Bundle; |
| 16 import android.os.Handler; |
| 17 import android.os.PatternMatcher; |
| 18 import android.support.v4.content.LocalBroadcastManager; |
| 19 import android.view.KeyEvent; |
| 20 import android.view.MotionEvent; |
| 21 import android.view.WindowManager; |
| 22 import android.widget.FrameLayout; |
| 23 import android.widget.Toast; |
| 24 |
| 25 import org.chromium.base.Log; |
| 26 import org.chromium.base.annotations.JNINamespace; |
| 27 import org.chromium.content.browser.ActivityContentVideoViewEmbedder; |
| 28 import org.chromium.content.browser.ContentVideoViewEmbedder; |
| 29 import org.chromium.content.browser.ContentView; |
| 30 import org.chromium.content.browser.ContentViewClient; |
| 31 import org.chromium.content.browser.ContentViewCore; |
| 32 import org.chromium.content.browser.ContentViewRenderView; |
| 33 import org.chromium.content_public.browser.WebContents; |
| 34 import org.chromium.ui.base.ViewAndroidDelegate; |
| 35 import org.chromium.ui.base.WindowAndroid; |
| 36 |
| 37 /** |
| 38 * Activity for displaying a WebContents in CastShell. |
| 39 * |
| 40 * Typically, this class is controlled by CastContentWindowAndroid, which will |
| 41 * start a new instance of this activity. If the CastContentWindowAndroid is |
| 42 * destroyed, CastWebContentsActivity should finish(). Similarily, if this |
| 43 * activity is destroyed, CastContentWindowAndroid should be notified by intent. |
| 44 */ |
| 45 @JNINamespace("chromecast::shell") |
| 46 public class CastWebContentsActivity extends Activity { |
| 47 private static final String TAG = "cr_CastWebActivity"; |
| 48 private static final boolean DEBUG = true; |
| 49 |
| 50 private Handler mHandler; |
| 51 private String mInstanceId; |
| 52 private BroadcastReceiver mWindowDestroyedBroadcastReceiver; |
| 53 private IntentFilter mWindowDestroyedIntentFilter; |
| 54 private FrameLayout mCastWebContentsLayout; |
| 55 private AudioManager mAudioManager; |
| 56 private ContentViewClient mContentViewClient; |
| 57 private ContentViewRenderView mContentViewRenderView; |
| 58 private WindowAndroid mWindow; |
| 59 private ContentViewCore mContentViewCore; |
| 60 private ContentView mContentView; |
| 61 |
| 62 private static final int TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS = 300; |
| 63 public static final String ACTION_DATA_SCHEME = "cast"; |
| 64 public static final String ACTION_DATA_AUTHORITY = "webcontents"; |
| 65 |
| 66 public static final String ACTION_EXTRA_WEB_CONTENTS = |
| 67 "com.google.android.apps.castshell.intent.extra.WEB_CONTENTS"; |
| 68 public static final String ACTION_EXTRA_KEY_CODE = |
| 69 "com.google.android.apps.castshell.intent.extra.KEY_CODE"; |
| 70 public static final String ACTION_KEY_EVENT = |
| 71 "com.google.android.apps.castshell.intent.action.KEY_EVENT"; |
| 72 public static final String ACTION_STOP_ACTIVITY = |
| 73 "com.google.android.apps.castshell.intent.action.STOP_ACTIVITY"; |
| 74 public static final String ACTION_ACTIVITY_STOPPED = |
| 75 "com.google.android.apps.castshell.intent.action.ACTIVITY_STOPPED"; |
| 76 |
| 77 @Override |
| 78 protected void onCreate(final Bundle savedInstanceState) { |
| 79 if (DEBUG) Log.d(TAG, "onCreate"); |
| 80 super.onCreate(savedInstanceState); |
| 81 |
| 82 mHandler = new Handler(); |
| 83 |
| 84 // TODO(derekjchow): Remove this call. |
| 85 if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { |
| 86 Toast.makeText(this, R.string.browser_process_initialization_failed,
Toast.LENGTH_SHORT) |
| 87 .show(); |
| 88 finish(); |
| 89 } |
| 90 |
| 91 // Whenever our app is visible, volume controls should modify the music
stream. |
| 92 // For more information read: |
| 93 // http://developer.android.com/training/managing-audio/volume-playback.
html |
| 94 setVolumeControlStream(AudioManager.STREAM_MUSIC); |
| 95 |
| 96 // Set flags to both exit sleep mode when this activity starts and |
| 97 // avoid entering sleep mode while playing media. We cannot distinguish |
| 98 // between video and audio so this applies to both. |
| 99 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); |
| 100 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| 101 |
| 102 mAudioManager = CastAudioManager.getAudioManager(this); |
| 103 |
| 104 setContentView(R.layout.cast_web_contents_activity); |
| 105 |
| 106 mWindow = new WindowAndroid(this); |
| 107 mContentViewRenderView = new ContentViewRenderView(this) { |
| 108 @Override |
| 109 protected void onReadyToRender() { |
| 110 setOverlayVideoMode(true); |
| 111 } |
| 112 }; |
| 113 mContentViewRenderView.onNativeLibraryLoaded(mWindow); |
| 114 // Setting the background color to black avoids rendering a white splash
screen |
| 115 // before the players are loaded. See crbug/307113 for details. |
| 116 mContentViewRenderView.setSurfaceViewBackgroundColor(Color.BLACK); |
| 117 |
| 118 mCastWebContentsLayout = (FrameLayout) findViewById(R.id.web_contents_co
ntainer); |
| 119 mCastWebContentsLayout.addView(mContentViewRenderView, |
| 120 new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARE
NT, |
| 121 FrameLayout.LayoutParams.MATCH_PARENT)); |
| 122 |
| 123 Intent intent = getIntent(); |
| 124 handleIntent(intent); |
| 125 } |
| 126 |
| 127 protected void handleIntent(Intent intent) { |
| 128 intent.setExtrasClassLoader(WebContents.class.getClassLoader()); |
| 129 mInstanceId = intent.getData().getPath(); |
| 130 |
| 131 final String instanceId = mInstanceId; |
| 132 if (mWindowDestroyedBroadcastReceiver != null) { |
| 133 LocalBroadcastManager.getInstance(this).unregisterReceiver( |
| 134 mWindowDestroyedBroadcastReceiver); |
| 135 } |
| 136 mWindowDestroyedBroadcastReceiver = new BroadcastReceiver() { |
| 137 @Override |
| 138 public void onReceive(Context context, Intent intent) { |
| 139 detachWebContentsIfAny(); |
| 140 maybeFinishLater(); |
| 141 } |
| 142 }; |
| 143 mWindowDestroyedIntentFilter = new IntentFilter(); |
| 144 mWindowDestroyedIntentFilter.addDataScheme(intent.getData().getScheme())
; |
| 145 mWindowDestroyedIntentFilter.addDataAuthority(intent.getData().getAuthor
ity(), null); |
| 146 mWindowDestroyedIntentFilter.addDataPath(mInstanceId, PatternMatcher.PAT
TERN_LITERAL); |
| 147 mWindowDestroyedIntentFilter.addAction(ACTION_STOP_ACTIVITY); |
| 148 LocalBroadcastManager.getInstance(this).registerReceiver( |
| 149 mWindowDestroyedBroadcastReceiver, mWindowDestroyedIntentFilter)
; |
| 150 |
| 151 WebContents webContents = |
| 152 (WebContents) intent.getParcelableExtra(ACTION_EXTRA_WEB_CONTENT
S); |
| 153 if (webContents == null) { |
| 154 Log.e(TAG, "Received null WebContents in intent."); |
| 155 maybeFinishLater(); |
| 156 return; |
| 157 } |
| 158 |
| 159 detachWebContentsIfAny(); |
| 160 showWebContents(webContents); |
| 161 } |
| 162 |
| 163 @Override |
| 164 protected void onNewIntent(Intent intent) { |
| 165 if (DEBUG) Log.d(TAG, "onNewIntent"); |
| 166 |
| 167 // If we're currently finishing this activity, we should start a new act
ivity to |
| 168 // display the new app. |
| 169 if (isFinishing()) { |
| 170 Log.d(TAG, "Activity is finishing, starting new activity."); |
| 171 int flags = intent.getFlags(); |
| 172 flags = flags & ~Intent.FLAG_ACTIVITY_SINGLE_TOP; |
| 173 intent.setFlags(flags); |
| 174 startActivity(intent); |
| 175 return; |
| 176 } |
| 177 |
| 178 handleIntent(intent); |
| 179 } |
| 180 |
| 181 @Override |
| 182 protected void onDestroy() { |
| 183 if (DEBUG) Log.d(TAG, "onDestroy"); |
| 184 super.onDestroy(); |
| 185 } |
| 186 |
| 187 @Override |
| 188 protected void onStart() { |
| 189 if (DEBUG) Log.d(TAG, "onStart"); |
| 190 super.onStart(); |
| 191 } |
| 192 |
| 193 @Override |
| 194 protected void onStop() { |
| 195 if (DEBUG) Log.d(TAG, "onStop"); |
| 196 |
| 197 detachWebContentsIfAny(); |
| 198 releaseStreamMuteIfNecessary(); |
| 199 super.onStop(); |
| 200 } |
| 201 |
| 202 @Override |
| 203 protected void onResume() { |
| 204 if (DEBUG) Log.d(TAG, "onResume"); |
| 205 super.onResume(); |
| 206 |
| 207 if (mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, |
| 208 AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQ
UEST_GRANTED) { |
| 209 Log.e(TAG, "Failed to obtain audio focus"); |
| 210 } |
| 211 } |
| 212 |
| 213 @Override |
| 214 protected void onPause() { |
| 215 if (DEBUG) Log.d(TAG, "onPause"); |
| 216 super.onPause(); |
| 217 |
| 218 // Release the audio focus. Note that releasing audio focus does not sto
p audio playback, |
| 219 // it just notifies the framework that this activity has stopped playing
audio. |
| 220 if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQ
UEST_GRANTED) { |
| 221 Log.e(TAG, "Failed to abandon audio focus"); |
| 222 } |
| 223 } |
| 224 |
| 225 @Override |
| 226 public boolean dispatchKeyEvent(KeyEvent event) { |
| 227 if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); |
| 228 int keyCode = event.getKeyCode(); |
| 229 int action = event.getAction(); |
| 230 |
| 231 // Similar condition for all single-click events. |
| 232 if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { |
| 233 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER |
| 234 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE |
| 235 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY |
| 236 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE |
| 237 || keyCode == KeyEvent.KEYCODE_MEDIA_STOP |
| 238 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT |
| 239 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { |
| 240 Intent intent = new Intent(ACTION_KEY_EVENT, getInstanceUri()); |
| 241 intent.putExtra(ACTION_EXTRA_KEY_CODE, keyCode); |
| 242 LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent
); |
| 243 return true; |
| 244 } |
| 245 } |
| 246 |
| 247 if (keyCode == KeyEvent.KEYCODE_BACK) { |
| 248 return super.dispatchKeyEvent(event); |
| 249 } |
| 250 return false; |
| 251 } |
| 252 |
| 253 @Override |
| 254 public boolean dispatchGenericMotionEvent(MotionEvent ev) { |
| 255 return false; |
| 256 } |
| 257 |
| 258 @Override |
| 259 public boolean dispatchKeyShortcutEvent(KeyEvent event) { |
| 260 return false; |
| 261 } |
| 262 |
| 263 @Override |
| 264 public boolean dispatchTouchEvent(MotionEvent ev) { |
| 265 return false; |
| 266 } |
| 267 |
| 268 @Override |
| 269 public boolean dispatchTrackballEvent(MotionEvent ev) { |
| 270 return false; |
| 271 } |
| 272 |
| 273 @SuppressWarnings("deprecation") |
| 274 private void releaseStreamMuteIfNecessary() { |
| 275 AudioManager audioManager = CastAudioManager.getAudioManager(this); |
| 276 boolean isMuted = false; |
| 277 try { |
| 278 isMuted = (Boolean) audioManager.getClass() |
| 279 .getMethod("isStreamMute", int.class) |
| 280 .invoke(audioManager, AudioManager.STREAM_MUSIC); |
| 281 } catch (Exception e) { |
| 282 Log.e(TAG, "Cannot call AudioManager.isStreamMute().", e); |
| 283 } |
| 284 |
| 285 if (isMuted) { |
| 286 // Note: this is a no-op on fixed-volume devices. |
| 287 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); |
| 288 } |
| 289 } |
| 290 |
| 291 // Closes this activity if a new WebContents is not being displayed. |
| 292 private void maybeFinishLater() { |
| 293 Log.d(TAG, "maybeFinishLater"); |
| 294 final String currentInstanceId = mInstanceId; |
| 295 mHandler.postDelayed(new Runnable() { |
| 296 @Override |
| 297 public void run() { |
| 298 if (currentInstanceId == mInstanceId) { |
| 299 Log.d(TAG, "Finishing."); |
| 300 finish(); |
| 301 } |
| 302 } |
| 303 }, TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS); |
| 304 } |
| 305 |
| 306 // Sets webContents to be the currently displayed webContents. |
| 307 private void showWebContents(WebContents webContents) { |
| 308 if (DEBUG) Log.d(TAG, "showWebContents"); |
| 309 |
| 310 // Set ContentVideoViewEmbedder to allow video playback. |
| 311 nativeSetContentVideoViewEmbedder(webContents, new ActivityContentVideoV
iewEmbedder(this)); |
| 312 |
| 313 // TODO(derekjchow): productVersion |
| 314 mContentViewCore = new ContentViewCore(this, ""); |
| 315 mContentView = ContentView.createContentView(this, mContentViewCore); |
| 316 mContentViewCore.initialize(ViewAndroidDelegate.createBasicDelegate(mCon
tentView), |
| 317 mContentView, webContents, mWindow); |
| 318 mContentViewClient = new ContentViewClient(); |
| 319 mContentViewCore.setContentViewClient(mContentViewClient); |
| 320 // Enable display of current webContents. |
| 321 if (getParent() != null) mContentViewCore.onShow(); |
| 322 mCastWebContentsLayout.addView( |
| 323 mContentView, new FrameLayout.LayoutParams(FrameLayout.LayoutPar
ams.MATCH_PARENT, |
| 324 FrameLayout.LayoutPar
ams.MATCH_PARENT)); |
| 325 mContentView.requestFocus(); |
| 326 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore); |
| 327 } |
| 328 |
| 329 // Remove the currently displayed webContents. no-op if nothing is being dis
played. |
| 330 private void detachWebContentsIfAny() { |
| 331 if (DEBUG) Log.d(TAG, "detachWebContentsIfAny"); |
| 332 if (mContentView != null) { |
| 333 mCastWebContentsLayout.removeView(mContentView); |
| 334 mContentView = null; |
| 335 mContentViewCore = null; |
| 336 |
| 337 // Inform CastContentWindowAndroid we're detaching. |
| 338 Intent intent = new Intent(ACTION_ACTIVITY_STOPPED, getInstanceUri()
); |
| 339 LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent); |
| 340 } |
| 341 } |
| 342 |
| 343 private Uri getInstanceUri() { |
| 344 Uri instanceUri = new Uri.Builder() |
| 345 .scheme(CastWebContentsActivity.ACTION_DATA_SCHEME) |
| 346 .authority(CastWebContentsActivity.ACTION_DATA_AUTHORITY) |
| 347 .path(mInstanceId) |
| 348 .build(); |
| 349 return instanceUri; |
| 350 } |
| 351 |
| 352 private native void nativeSetContentVideoViewEmbedder( |
| 353 WebContents webContents, ContentVideoViewEmbedder embedder); |
| 354 } |
OLD | NEW |