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 |