OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chromecast.shell; | 5 package org.chromium.chromecast.shell; |
6 | 6 |
7 import android.app.Activity; | 7 import android.app.Activity; |
8 import android.content.BroadcastReceiver; | 8 import android.content.BroadcastReceiver; |
9 import android.content.Context; | 9 import android.content.Context; |
10 import android.content.Intent; | 10 import android.content.Intent; |
11 import android.content.IntentFilter; | 11 import android.content.IntentFilter; |
12 import android.graphics.Color; | 12 import android.graphics.Color; |
13 import android.media.AudioManager; | 13 import android.media.AudioManager; |
14 import android.net.Uri; | |
15 import android.os.Bundle; | 14 import android.os.Bundle; |
16 import android.os.Handler; | 15 import android.os.Handler; |
17 import android.os.PatternMatcher; | 16 import android.os.PatternMatcher; |
18 import android.support.v4.content.LocalBroadcastManager; | 17 import android.support.v4.content.LocalBroadcastManager; |
19 import android.view.KeyEvent; | 18 import android.view.KeyEvent; |
20 import android.view.MotionEvent; | 19 import android.view.MotionEvent; |
21 import android.view.WindowManager; | 20 import android.view.WindowManager; |
22 import android.widget.FrameLayout; | 21 import android.widget.FrameLayout; |
23 import android.widget.Toast; | 22 import android.widget.Toast; |
24 | 23 |
25 import org.chromium.base.Log; | 24 import org.chromium.base.Log; |
26 import org.chromium.base.annotations.JNINamespace; | 25 import org.chromium.base.annotations.JNINamespace; |
27 import org.chromium.content.browser.ActivityContentVideoViewEmbedder; | 26 import org.chromium.content.browser.ActivityContentVideoViewEmbedder; |
28 import org.chromium.content.browser.ContentVideoViewEmbedder; | 27 import org.chromium.content.browser.ContentVideoViewEmbedder; |
29 import org.chromium.content.browser.ContentView; | 28 import org.chromium.content.browser.ContentView; |
30 import org.chromium.content.browser.ContentViewCore; | 29 import org.chromium.content.browser.ContentViewCore; |
31 import org.chromium.content.browser.ContentViewRenderView; | 30 import org.chromium.content.browser.ContentViewRenderView; |
32 import org.chromium.content_public.browser.WebContents; | 31 import org.chromium.content_public.browser.WebContents; |
33 import org.chromium.ui.base.ViewAndroidDelegate; | 32 import org.chromium.ui.base.ViewAndroidDelegate; |
34 import org.chromium.ui.base.WindowAndroid; | 33 import org.chromium.ui.base.WindowAndroid; |
35 | 34 |
36 /** | 35 /** |
37 * Activity for displaying a WebContents in CastShell. | 36 * Activity for displaying a WebContents in CastShell. |
38 * | 37 * <p> |
39 * Typically, this class is controlled by CastContentWindowAndroid, which will | 38 * Typically, this class is controlled by CastContentWindowAndroid, which will |
40 * start a new instance of this activity. If the CastContentWindowAndroid is | 39 * start a new instance of this activity. If the CastContentWindowAndroid is |
41 * destroyed, CastWebContentsActivity should finish(). Similarily, if this | 40 * destroyed, CastWebContentsActivity should finish(). Similarily, if this |
42 * activity is destroyed, CastContentWindowAndroid should be notified by intent. | 41 * activity is destroyed, CastContentWindowAndroid should be notified by intent. |
43 */ | 42 */ |
44 @JNINamespace("chromecast::shell") | 43 @JNINamespace("chromecast::shell") |
45 public class CastWebContentsActivity extends Activity { | 44 public class CastWebContentsActivity extends Activity { |
46 private static final String TAG = "cr_CastWebActivity"; | 45 private static final String TAG = "cr_CastWebActivity"; |
47 private static final boolean DEBUG = true; | 46 private static final boolean DEBUG = true; |
48 | 47 |
49 private Handler mHandler; | 48 private Handler mHandler; |
50 private String mInstanceId; | 49 private String mInstanceId; |
51 private BroadcastReceiver mWindowDestroyedBroadcastReceiver; | 50 private BroadcastReceiver mWindowDestroyedBroadcastReceiver; |
52 private IntentFilter mWindowDestroyedIntentFilter; | 51 private IntentFilter mWindowDestroyedIntentFilter; |
53 private FrameLayout mCastWebContentsLayout; | 52 private FrameLayout mCastWebContentsLayout; |
54 private AudioManager mAudioManager; | 53 private AudioManager mAudioManager; |
55 private ContentViewRenderView mContentViewRenderView; | 54 private ContentViewRenderView mContentViewRenderView; |
56 private WindowAndroid mWindow; | 55 private WindowAndroid mWindow; |
57 private ContentViewCore mContentViewCore; | 56 private ContentViewCore mContentViewCore; |
58 private ContentView mContentView; | 57 private ContentView mContentView; |
59 private boolean mReceivedUserLeave = false; | 58 private boolean mReceivedUserLeave = false; |
60 | 59 |
61 private static final int TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS = 300; | 60 private static final int TEARDOWN_GRACE_PERIOD_TIMEOUT_MILLIS = 300; |
62 public static final String ACTION_DATA_SCHEME = "cast"; | |
63 public static final String ACTION_DATA_AUTHORITY = "webcontents"; | |
64 | 61 |
65 public static final String ACTION_EXTRA_WEB_CONTENTS = | |
66 "com.google.android.apps.castshell.intent.extra.WEB_CONTENTS"; | |
67 public static final String ACTION_EXTRA_KEY_CODE = | |
68 "com.google.android.apps.castshell.intent.extra.KEY_CODE"; | |
69 public static final String ACTION_KEY_EVENT = | |
70 "com.google.android.apps.castshell.intent.action.KEY_EVENT"; | |
71 public static final String ACTION_STOP_ACTIVITY = | 62 public static final String ACTION_STOP_ACTIVITY = |
72 "com.google.android.apps.castshell.intent.action.STOP_ACTIVITY"; | 63 "com.google.android.apps.castshell.intent.action.STOP_ACTIVITY"; |
73 public static final String ACTION_ACTIVITY_STOPPED = | |
74 "com.google.android.apps.castshell.intent.action.ACTIVITY_STOPPED"; | |
75 | 64 |
76 /* | 65 /* |
77 * Intended to be called from "onStop" to determine if this is a "legitimate
" stop or not. | 66 * Intended to be called from "onStop" to determine if this is a "legitimate
" stop or not. |
78 * When starting CastShellActivity from the TV in sleep mode, an extra onPau
se/onStop will be | 67 * When starting CastShellActivity from the TV in sleep mode, an extra onPau
se/onStop will be |
79 * fired. | 68 * fired. |
80 * Details: http://stackoverflow.com/questions/25369909/ | 69 * Details: http://stackoverflow.com/questions/25369909/ |
81 * We use onUserLeaveHint to determine if the onPause/onStop called because
of user intent. | 70 * We use onUserLeaveHint to determine if the onPause/onStop called because
of user intent. |
82 */ | 71 */ |
83 private boolean isStopping() { | 72 private boolean isStopping() { |
84 return mReceivedUserLeave; | 73 return mReceivedUserLeave; |
85 } | 74 } |
86 | 75 |
87 @Override | 76 @Override |
88 protected void onCreate(final Bundle savedInstanceState) { | 77 protected void onCreate(final Bundle savedInstanceState) { |
89 if (DEBUG) Log.d(TAG, "onCreate"); | 78 if (DEBUG) Log.d(TAG, "onCreate"); |
90 super.onCreate(savedInstanceState); | 79 super.onCreate(savedInstanceState); |
91 | 80 |
92 mHandler = new Handler(); | 81 mHandler = new Handler(); |
93 | 82 |
94 // TODO(derekjchow): Remove this call. | |
95 if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { | 83 if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { |
96 Toast.makeText(this, R.string.browser_process_initialization_failed,
Toast.LENGTH_SHORT) | 84 Toast.makeText(this, R.string.browser_process_initialization_failed,
Toast.LENGTH_SHORT) |
97 .show(); | 85 .show(); |
98 finish(); | 86 finish(); |
99 } | 87 } |
100 | 88 |
101 // Whenever our app is visible, volume controls should modify the music
stream. | 89 // Whenever our app is visible, volume controls should modify the music
stream. |
102 // For more information read: | 90 // For more information read: |
103 // http://developer.android.com/training/managing-audio/volume-playback.
html | 91 // http://developer.android.com/training/managing-audio/volume-playback.
html |
104 setVolumeControlStream(AudioManager.STREAM_MUSIC); | 92 setVolumeControlStream(AudioManager.STREAM_MUSIC); |
(...skipping 26 matching lines...) Expand all Loading... |
131 FrameLayout.LayoutParams.MATCH_PARENT)); | 119 FrameLayout.LayoutParams.MATCH_PARENT)); |
132 | 120 |
133 Intent intent = getIntent(); | 121 Intent intent = getIntent(); |
134 handleIntent(intent); | 122 handleIntent(intent); |
135 } | 123 } |
136 | 124 |
137 protected void handleIntent(Intent intent) { | 125 protected void handleIntent(Intent intent) { |
138 intent.setExtrasClassLoader(WebContents.class.getClassLoader()); | 126 intent.setExtrasClassLoader(WebContents.class.getClassLoader()); |
139 mInstanceId = intent.getData().getPath(); | 127 mInstanceId = intent.getData().getPath(); |
140 | 128 |
141 final String instanceId = mInstanceId; | |
142 if (mWindowDestroyedBroadcastReceiver != null) { | 129 if (mWindowDestroyedBroadcastReceiver != null) { |
143 LocalBroadcastManager.getInstance(this).unregisterReceiver( | 130 LocalBroadcastManager.getInstance(this).unregisterReceiver( |
144 mWindowDestroyedBroadcastReceiver); | 131 mWindowDestroyedBroadcastReceiver); |
145 } | 132 } |
146 mWindowDestroyedBroadcastReceiver = new BroadcastReceiver() { | 133 mWindowDestroyedBroadcastReceiver = new BroadcastReceiver() { |
147 @Override | 134 @Override |
148 public void onReceive(Context context, Intent intent) { | 135 public void onReceive(Context context, Intent intent) { |
149 detachWebContentsIfAny(); | 136 detachWebContentsIfAny(); |
150 maybeFinishLater(); | 137 maybeFinishLater(); |
151 } | 138 } |
152 }; | 139 }; |
153 mWindowDestroyedIntentFilter = new IntentFilter(); | 140 mWindowDestroyedIntentFilter = new IntentFilter(); |
154 mWindowDestroyedIntentFilter.addDataScheme(intent.getData().getScheme())
; | 141 mWindowDestroyedIntentFilter.addDataScheme(intent.getData().getScheme())
; |
155 mWindowDestroyedIntentFilter.addDataAuthority(intent.getData().getAuthor
ity(), null); | 142 mWindowDestroyedIntentFilter.addDataAuthority(intent.getData().getAuthor
ity(), null); |
156 mWindowDestroyedIntentFilter.addDataPath(mInstanceId, PatternMatcher.PAT
TERN_LITERAL); | 143 mWindowDestroyedIntentFilter.addDataPath(mInstanceId, PatternMatcher.PAT
TERN_LITERAL); |
157 mWindowDestroyedIntentFilter.addAction(ACTION_STOP_ACTIVITY); | 144 mWindowDestroyedIntentFilter.addAction(ACTION_STOP_ACTIVITY); |
158 LocalBroadcastManager.getInstance(this).registerReceiver( | 145 LocalBroadcastManager.getInstance(this).registerReceiver( |
159 mWindowDestroyedBroadcastReceiver, mWindowDestroyedIntentFilter)
; | 146 mWindowDestroyedBroadcastReceiver, mWindowDestroyedIntentFilter)
; |
160 | 147 |
161 WebContents webContents = | 148 WebContents webContents = (WebContents) intent.getParcelableExtra( |
162 (WebContents) intent.getParcelableExtra(ACTION_EXTRA_WEB_CONTENT
S); | 149 CastWebContentsComponent.ACTION_EXTRA_WEB_CONTENTS); |
163 if (webContents == null) { | 150 if (webContents == null) { |
164 Log.e(TAG, "Received null WebContents in intent."); | 151 Log.e(TAG, "Received null WebContents in intent."); |
165 maybeFinishLater(); | 152 maybeFinishLater(); |
166 return; | 153 return; |
167 } | 154 } |
168 | 155 |
169 detachWebContentsIfAny(); | 156 detachWebContentsIfAny(); |
170 showWebContents(webContents); | 157 showWebContents(webContents); |
171 } | 158 } |
172 | 159 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 releaseStreamMuteIfNecessary(); | 195 releaseStreamMuteIfNecessary(); |
209 } | 196 } |
210 super.onStop(); | 197 super.onStop(); |
211 } | 198 } |
212 | 199 |
213 @Override | 200 @Override |
214 protected void onResume() { | 201 protected void onResume() { |
215 if (DEBUG) Log.d(TAG, "onResume"); | 202 if (DEBUG) Log.d(TAG, "onResume"); |
216 super.onResume(); | 203 super.onResume(); |
217 | 204 |
218 if (mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, | 205 if (mAudioManager.requestAudioFocus( |
219 AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQ
UEST_GRANTED) { | 206 null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAI
N) |
| 207 != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
220 Log.e(TAG, "Failed to obtain audio focus"); | 208 Log.e(TAG, "Failed to obtain audio focus"); |
221 } | 209 } |
222 } | 210 } |
223 | 211 |
224 @Override | 212 @Override |
225 protected void onPause() { | 213 protected void onPause() { |
226 if (DEBUG) Log.d(TAG, "onPause"); | 214 if (DEBUG) Log.d(TAG, "onPause"); |
227 super.onPause(); | 215 super.onPause(); |
228 | 216 |
229 // Release the audio focus. Note that releasing audio focus does not sto
p audio playback, | 217 // Release the audio focus. Note that releasing audio focus does not sto
p audio playback, |
(...skipping 17 matching lines...) Expand all Loading... |
247 | 235 |
248 // Similar condition for all single-click events. | 236 // Similar condition for all single-click events. |
249 if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { | 237 if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { |
250 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER | 238 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER |
251 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE | 239 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE |
252 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY | 240 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY |
253 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE | 241 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE |
254 || keyCode == KeyEvent.KEYCODE_MEDIA_STOP | 242 || keyCode == KeyEvent.KEYCODE_MEDIA_STOP |
255 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT | 243 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT |
256 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { | 244 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { |
257 Intent intent = new Intent(ACTION_KEY_EVENT, getInstanceUri()); | 245 CastWebContentsComponent.onKeyDown(this, mInstanceId, keyCode); |
258 intent.putExtra(ACTION_EXTRA_KEY_CODE, keyCode); | |
259 LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent
); | |
260 | 246 |
261 // Stop key should end the entire session. | 247 // Stop key should end the entire session. |
262 if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) { | 248 if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) { |
263 mReceivedUserLeave = true; | 249 mReceivedUserLeave = true; |
264 finish(); | 250 finish(); |
265 } | 251 } |
266 | 252 |
267 return true; | 253 return true; |
268 } | 254 } |
269 } | 255 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 // Set ContentVideoViewEmbedder to allow video playback. | 321 // Set ContentVideoViewEmbedder to allow video playback. |
336 nativeSetContentVideoViewEmbedder(webContents, new ActivityContentVideoV
iewEmbedder(this)); | 322 nativeSetContentVideoViewEmbedder(webContents, new ActivityContentVideoV
iewEmbedder(this)); |
337 | 323 |
338 // TODO(derekjchow): productVersion | 324 // TODO(derekjchow): productVersion |
339 mContentViewCore = new ContentViewCore(this, ""); | 325 mContentViewCore = new ContentViewCore(this, ""); |
340 mContentView = ContentView.createContentView(this, mContentViewCore); | 326 mContentView = ContentView.createContentView(this, mContentViewCore); |
341 mContentViewCore.initialize(ViewAndroidDelegate.createBasicDelegate(mCon
tentView), | 327 mContentViewCore.initialize(ViewAndroidDelegate.createBasicDelegate(mCon
tentView), |
342 mContentView, webContents, mWindow); | 328 mContentView, webContents, mWindow); |
343 // Enable display of current webContents. | 329 // Enable display of current webContents. |
344 if (getParent() != null) mContentViewCore.onShow(); | 330 if (getParent() != null) mContentViewCore.onShow(); |
345 mCastWebContentsLayout.addView( | 331 mCastWebContentsLayout.addView(mContentView, |
346 mContentView, new FrameLayout.LayoutParams(FrameLayout.LayoutPar
ams.MATCH_PARENT, | 332 new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARE
NT, |
347 FrameLayout.LayoutPar
ams.MATCH_PARENT)); | 333 FrameLayout.LayoutParams.MATCH_PARENT)); |
348 mContentView.requestFocus(); | 334 mContentView.requestFocus(); |
349 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore); | 335 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore); |
350 } | 336 } |
351 | 337 |
352 // Remove the currently displayed webContents. no-op if nothing is being dis
played. | 338 // Remove the currently displayed webContents. no-op if nothing is being dis
played. |
353 private void detachWebContentsIfAny() { | 339 private void detachWebContentsIfAny() { |
354 if (DEBUG) Log.d(TAG, "detachWebContentsIfAny"); | 340 if (DEBUG) Log.d(TAG, "detachWebContentsIfAny"); |
355 if (mContentView != null) { | 341 if (mContentView != null) { |
356 mCastWebContentsLayout.removeView(mContentView); | 342 mCastWebContentsLayout.removeView(mContentView); |
357 mContentView = null; | 343 mContentView = null; |
358 mContentViewCore = null; | 344 mContentViewCore = null; |
359 | 345 |
360 // Inform CastContentWindowAndroid we're detaching. | 346 CastWebContentsComponent.onComponentClosed(this, mInstanceId); |
361 Intent intent = new Intent(ACTION_ACTIVITY_STOPPED, getInstanceUri()
); | |
362 LocalBroadcastManager.getInstance(this).sendBroadcastSync(intent); | |
363 } | 347 } |
364 } | 348 } |
365 | 349 |
366 private Uri getInstanceUri() { | |
367 Uri instanceUri = new Uri.Builder() | |
368 .scheme(CastWebContentsActivity.ACTION_DATA_SCHEME) | |
369 .authority(CastWebContentsActivity.ACTION_DATA_AUTHORITY) | |
370 .path(mInstanceId) | |
371 .build(); | |
372 return instanceUri; | |
373 } | |
374 | |
375 private native void nativeSetContentVideoViewEmbedder( | 350 private native void nativeSetContentVideoViewEmbedder( |
376 WebContents webContents, ContentVideoViewEmbedder embedder); | 351 WebContents webContents, ContentVideoViewEmbedder embedder); |
377 } | 352 } |
OLD | NEW |