| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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.media.AudioManager; | |
| 13 import android.net.Uri; | |
| 14 import android.os.Build; | |
| 15 import android.os.Bundle; | |
| 16 import android.support.v4.content.LocalBroadcastManager; | |
| 17 import android.view.KeyEvent; | |
| 18 import android.view.MotionEvent; | |
| 19 import android.view.WindowManager; | |
| 20 import android.widget.Toast; | |
| 21 | |
| 22 import org.chromium.base.Log; | |
| 23 import org.chromium.content.browser.ContentViewCore; | |
| 24 import org.chromium.ui.base.WindowAndroid; | |
| 25 | |
| 26 /** | |
| 27 * Activity for managing the Cast shell. | |
| 28 */ | |
| 29 public class CastShellActivity extends Activity { | |
| 30 private static final String TAG = "cr_CastShellActivity"; | |
| 31 private static final boolean DEBUG = false; | |
| 32 | |
| 33 private static final String ACTIVE_SHELL_URL_KEY = "activeUrl"; | |
| 34 | |
| 35 private CastWindowManager mCastWindowManager; | |
| 36 private AudioManager mAudioManager; | |
| 37 private BroadcastReceiver mBroadcastReceiver; | |
| 38 private boolean mReceivedUserLeave = false; | |
| 39 | |
| 40 // Native window instance. | |
| 41 // TODO(byungchul, gunsch): CastShellActivity, CastWindowAndroid, and native
CastWindowAndroid | |
| 42 // have a one-to-one relationship. Consider instantiating CastWindow here an
d CastWindow having | |
| 43 // this native shell instance. | |
| 44 private long mNativeCastWindow; | |
| 45 | |
| 46 /** | |
| 47 * Returns whether or not CastShellActivity should launch the browser startu
p sequence. | |
| 48 * Intended to be overridden. | |
| 49 */ | |
| 50 protected boolean shouldLaunchBrowser() { | |
| 51 return true; | |
| 52 } | |
| 53 | |
| 54 /** | |
| 55 * Intended to be called from "onStop" to determine if this is a "legitimate
" stop or not. | |
| 56 * When starting CastShellActivity from the TV in sleep mode, an extra onPau
se/onStop will be | |
| 57 * fired. | |
| 58 * Details: http://stackoverflow.com/questions/25369909/ | |
| 59 * We use onUserLeaveHint to determine if the onPause/onStop called because
of user intent. | |
| 60 */ | |
| 61 protected boolean isStopping() { | |
| 62 return mReceivedUserLeave; | |
| 63 } | |
| 64 | |
| 65 @Override | |
| 66 protected void onCreate(final Bundle savedInstanceState) { | |
| 67 if (DEBUG) Log.d(TAG, "onCreate"); | |
| 68 super.onCreate(savedInstanceState); | |
| 69 exitIfUrlMissing(); | |
| 70 | |
| 71 if (shouldLaunchBrowser()) { | |
| 72 if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { | |
| 73 Toast.makeText(this, | |
| 74 R.string.browser_process_initialization_failed, | |
| 75 Toast.LENGTH_SHORT).show(); | |
| 76 finish(); | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 // Whenever our app is visible, volume controls should modify the music
stream. | |
| 81 // For more information read: | |
| 82 // http://developer.android.com/training/managing-audio/volume-playback.
html | |
| 83 setVolumeControlStream(AudioManager.STREAM_MUSIC); | |
| 84 | |
| 85 // Set flags to both exit sleep mode when this activity starts and | |
| 86 // avoid entering sleep mode while playing media. We cannot distinguish | |
| 87 // between video and audio so this applies to both. | |
| 88 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); | |
| 89 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |
| 90 | |
| 91 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); | |
| 92 | |
| 93 setContentView(R.layout.cast_shell_activity); | |
| 94 mCastWindowManager = (CastWindowManager) findViewById(R.id.shell_contain
er); | |
| 95 mCastWindowManager.setDelegate(new CastWindowManager.Delegate() { | |
| 96 @Override | |
| 97 public void onCreated() { | |
| 98 } | |
| 99 | |
| 100 @Override | |
| 101 public void onClosed() { | |
| 102 mNativeCastWindow = 0; | |
| 103 mCastWindowManager.setDelegate(null); | |
| 104 finish(); | |
| 105 } | |
| 106 }); | |
| 107 mCastWindowManager.setWindow(new WindowAndroid(this)); | |
| 108 | |
| 109 registerBroadcastReceiver(); | |
| 110 | |
| 111 String url = getIntent().getDataString(); | |
| 112 Log.d(TAG, "onCreate startupUrl: %s", url); | |
| 113 mNativeCastWindow = mCastWindowManager.launchCastWindow(url); | |
| 114 } | |
| 115 | |
| 116 @Override | |
| 117 protected void onDestroy() { | |
| 118 if (DEBUG) Log.d(TAG, "onDestroy"); | |
| 119 | |
| 120 unregisterBroadcastReceiver(); | |
| 121 | |
| 122 if (mNativeCastWindow != 0) { | |
| 123 mCastWindowManager.stopCastWindow(mNativeCastWindow, false /* gracef
ully */); | |
| 124 mNativeCastWindow = 0; | |
| 125 } | |
| 126 | |
| 127 super.onDestroy(); | |
| 128 } | |
| 129 | |
| 130 @Override | |
| 131 protected void onNewIntent(Intent intent) { | |
| 132 if (DEBUG) Log.d(TAG, "onNewIntent"); | |
| 133 // Only handle direct intents (e.g. "fling") if this activity is also ma
naging | |
| 134 // the browser process. | |
| 135 if (!shouldLaunchBrowser()) return; | |
| 136 | |
| 137 String url = intent.getDataString(); | |
| 138 Log.d(TAG, "onNewIntent: %s", url); | |
| 139 | |
| 140 // Reset broadcast intent uri and receiver. | |
| 141 setIntent(intent); | |
| 142 exitIfUrlMissing(); | |
| 143 getActiveCastWindow().loadUrl(url); | |
| 144 } | |
| 145 | |
| 146 @Override | |
| 147 protected void onStart() { | |
| 148 if (DEBUG) Log.d(TAG, "onStart"); | |
| 149 super.onStart(); | |
| 150 } | |
| 151 | |
| 152 @Override | |
| 153 protected void onStop() { | |
| 154 if (DEBUG) Log.d(TAG, "onStop, window focus = %d", hasWindowFocus()); | |
| 155 | |
| 156 if (isStopping()) { | |
| 157 // As soon as the cast app is no longer in the foreground, we ought
to immediately tear | |
| 158 // everything down. | |
| 159 finishGracefully(); | |
| 160 | |
| 161 // On pre-M devices, the device should be "unmuted" at the end of a
Cast application | |
| 162 // session, signaled by the activity exiting. See b/19964892. | |
| 163 if (Build.VERSION.SDK_INT < 23) { | |
| 164 releaseStreamMuteIfNecessary(); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 super.onStop(); | |
| 169 } | |
| 170 | |
| 171 @Override | |
| 172 protected void onResume() { | |
| 173 if (DEBUG) Log.d(TAG, "onResume"); | |
| 174 super.onResume(); | |
| 175 | |
| 176 // Inform ContentView that this activity is being shown. | |
| 177 ContentViewCore view = getActiveContentViewCore(); | |
| 178 if (view != null) view.onShow(); | |
| 179 | |
| 180 // Request audio focus so any other audio playback doesn't continue in t
he background. | |
| 181 if (mAudioManager.requestAudioFocus( | |
| 182 null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) | |
| 183 != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { | |
| 184 Log.e(TAG, "Failed to obtain audio focus"); | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 @Override | |
| 189 protected void onPause() { | |
| 190 if (DEBUG) Log.d(TAG, "onPause"); | |
| 191 | |
| 192 // Release the audio focus. Note that releasing audio focus does not sto
p audio playback, | |
| 193 // it just notifies the framework that this activity has stopped playing
audio. | |
| 194 if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQ
UEST_GRANTED) { | |
| 195 Log.e(TAG, "Failed to abandon audio focus"); | |
| 196 } | |
| 197 | |
| 198 ContentViewCore view = getActiveContentViewCore(); | |
| 199 if (view != null) view.onHide(); | |
| 200 | |
| 201 super.onPause(); | |
| 202 } | |
| 203 | |
| 204 @Override | |
| 205 protected void onUserLeaveHint() { | |
| 206 if (DEBUG) Log.d(TAG, "onUserLeaveHint"); | |
| 207 mReceivedUserLeave = true; | |
| 208 } | |
| 209 | |
| 210 protected void finishGracefully() { | |
| 211 if (mNativeCastWindow != 0) { | |
| 212 mCastWindowManager.stopCastWindow(mNativeCastWindow, true /* gracefu
lly */); | |
| 213 mNativeCastWindow = 0; | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 @SuppressWarnings("deprecation") | |
| 218 private void releaseStreamMuteIfNecessary() { | |
| 219 AudioManager audioManager = CastAudioManager.getAudioManager(this); | |
| 220 boolean isMuted = false; | |
| 221 try { | |
| 222 isMuted = (Boolean) audioManager.getClass().getMethod("isStreamMute"
, int.class) | |
| 223 .invoke(audioManager, AudioManager.STREAM_MUSIC); | |
| 224 } catch (Exception e) { | |
| 225 Log.e(TAG, "Cannot call AudioManager.isStreamMute().", e); | |
| 226 } | |
| 227 | |
| 228 if (isMuted) { | |
| 229 // Note: this is a no-op on fixed-volume devices. | |
| 230 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 private void registerBroadcastReceiver() { | |
| 235 if (mBroadcastReceiver == null) { | |
| 236 mBroadcastReceiver = new BroadcastReceiver() { | |
| 237 @Override | |
| 238 public void onReceive(Context context, Intent intent) { | |
| 239 Log.d(TAG, "Received intent: action=%s", intent.getAction())
; | |
| 240 if (CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS.equals(intent.
getAction())) { | |
| 241 mCastWindowManager.nativeEnableDevTools(true); | |
| 242 } else if (CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS.equals
( | |
| 243 intent.getAction())) { | |
| 244 mCastWindowManager.nativeEnableDevTools(false); | |
| 245 } | |
| 246 } | |
| 247 }; | |
| 248 } | |
| 249 | |
| 250 IntentFilter devtoolsBroadcastIntentFilter = new IntentFilter(); | |
| 251 devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_ENABLE_
DEV_TOOLS); | |
| 252 devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_DISABLE
_DEV_TOOLS); | |
| 253 LocalBroadcastManager.getInstance(this) | |
| 254 .registerReceiver(mBroadcastReceiver, devtoolsBroadcastIntentFil
ter); | |
| 255 } | |
| 256 | |
| 257 private void unregisterBroadcastReceiver() { | |
| 258 LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstan
ce(this); | |
| 259 broadcastManager.unregisterReceiver(mBroadcastReceiver); | |
| 260 } | |
| 261 | |
| 262 private void exitIfUrlMissing() { | |
| 263 Intent intent = getIntent(); | |
| 264 if (intent != null && intent.getData() != null && !intent.getData().equa
ls(Uri.EMPTY)) { | |
| 265 return; | |
| 266 } | |
| 267 // Log an exception so that the exit cause is obvious when reading the l
ogs. | |
| 268 Log.e(TAG, "Activity will not start", | |
| 269 new IllegalArgumentException("Intent did not contain a valid url
")); | |
| 270 System.exit(-1); | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * @return The currently visible {@link CastWindowAndroid} or null if one is
not showing. | |
| 275 */ | |
| 276 public CastWindowAndroid getActiveCastWindow() { | |
| 277 return mCastWindowManager.getActiveCastWindow(); | |
| 278 } | |
| 279 | |
| 280 /** | |
| 281 * @return The {@link ContentViewCore} owned by the currently visible {@link
CastWindowAndroid}, | |
| 282 * or null if one is not showing. | |
| 283 */ | |
| 284 public ContentViewCore getActiveContentViewCore() { | |
| 285 CastWindowAndroid shell = getActiveCastWindow(); | |
| 286 return shell != null ? shell.getContentViewCore() : null; | |
| 287 } | |
| 288 | |
| 289 @Override | |
| 290 public boolean onKeyUp(int keyCode, KeyEvent event) { | |
| 291 if (keyCode != KeyEvent.KEYCODE_BACK) { | |
| 292 return super.onKeyUp(keyCode, event); | |
| 293 } | |
| 294 | |
| 295 // Just finish this activity to go back to the previous activity or laun
cher. | |
| 296 finishGracefully(); | |
| 297 return true; | |
| 298 } | |
| 299 | |
| 300 @Override | |
| 301 public boolean dispatchKeyEvent(KeyEvent event) { | |
| 302 int keyCode = event.getKeyCode(); | |
| 303 if (keyCode == KeyEvent.KEYCODE_BACK) { | |
| 304 return super.dispatchKeyEvent(event); | |
| 305 } | |
| 306 return false; | |
| 307 } | |
| 308 | |
| 309 @Override | |
| 310 public boolean dispatchGenericMotionEvent(MotionEvent ev) { | |
| 311 return false; | |
| 312 } | |
| 313 | |
| 314 @Override | |
| 315 public boolean dispatchKeyShortcutEvent(KeyEvent event) { | |
| 316 return false; | |
| 317 } | |
| 318 | |
| 319 @Override | |
| 320 public boolean dispatchTouchEvent(MotionEvent ev) { | |
| 321 return false; | |
| 322 } | |
| 323 | |
| 324 @Override | |
| 325 public boolean dispatchTrackballEvent(MotionEvent ev) { | |
| 326 return false; | |
| 327 } | |
| 328 } | |
| OLD | NEW |