Chromium Code Reviews| 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) | |
|
byungchul
2016/12/16 22:52:22
indentation?
derekjchow1
2016/12/17 00:13:07
See other comment (clang-format-diff did it)!
| |
| 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.LayoutParams.MATCH_PARENT)); | |
|
byungchul
2016/12/16 22:52:22
This style doesn't look readable
derekjchow1
2016/12/17 00:13:07
ditto.
| |
| 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_SC HEME) | |
|
byungchul
2016/12/16 22:52:22
ditto
derekjchow1
2016/12/17 00:13:07
ditto
| |
| 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 |