CastShellActivity.java revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 5package org.chromium.chromecast.shell; 6 7import android.app.Activity; 8import android.content.BroadcastReceiver; 9import android.content.Context; 10import android.content.Intent; 11import android.content.IntentFilter; 12import android.media.AudioManager; 13import android.net.Uri; 14import android.os.Bundle; 15import android.support.v4.content.LocalBroadcastManager; 16import android.util.Log; 17import android.view.KeyEvent; 18import android.view.MotionEvent; 19import android.view.WindowManager; 20import android.widget.Toast; 21 22import org.chromium.base.CommandLine; 23import org.chromium.content.browser.ActivityContentVideoViewClient; 24import org.chromium.content.browser.ContentVideoViewClient; 25import org.chromium.content.browser.ContentViewClient; 26import org.chromium.content.browser.ContentViewCore; 27import org.chromium.ui.base.WindowAndroid; 28 29/** 30 * Activity for managing the Cast shell. 31 */ 32public class CastShellActivity extends Activity { 33 private static final String TAG = "CastShellActivity"; 34 35 private static final String ACTIVE_SHELL_URL_KEY = "activeUrl"; 36 private static final int DEFAULT_HEIGHT_PIXELS = 720; 37 public static final String ACTION_EXTRA_RESOLUTION_HEIGHT = 38 "org.chromium.chromecast.shell.intent.extra.RESOLUTION_HEIGHT"; 39 40 private CastWindowManager mCastWindowManager; 41 private AudioManager mAudioManager; 42 private BroadcastReceiver mBroadcastReceiver; 43 44 // Native window instance. 45 // TODO(byungchul, gunsch): CastShellActivity, CastWindowAndroid, and native CastWindowAndroid 46 // have a one-to-one relationship. Consider instantiating CastWindow here and CastWindow having 47 // this native shell instance. 48 private long mNativeCastWindow; 49 50 /** 51 * Returns whether or not CastShellActivity should launch the browser startup sequence. 52 * Intended to be overridden. 53 */ 54 protected boolean shouldLaunchBrowser() { 55 return true; 56 } 57 58 @Override 59 protected void onCreate(final Bundle savedInstanceState) { 60 super.onCreate(savedInstanceState); 61 exitIfUrlMissing(); 62 63 if (shouldLaunchBrowser()) { 64 if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { 65 Toast.makeText(this, 66 R.string.browser_process_initialization_failed, 67 Toast.LENGTH_SHORT).show(); 68 finish(); 69 } 70 } 71 72 // Whenever our app is visible, volume controls should modify the music stream. 73 // For more information read: 74 // http://developer.android.com/training/managing-audio/volume-playback.html 75 setVolumeControlStream(AudioManager.STREAM_MUSIC); 76 77 // Set flags to both exit sleep mode when this activity starts and 78 // avoid entering sleep mode while playing media. We cannot distinguish 79 // between video and audio so this applies to both. 80 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 81 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 82 83 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 84 85 setContentView(R.layout.cast_shell_activity); 86 mCastWindowManager = (CastWindowManager) findViewById(R.id.shell_container); 87 mCastWindowManager.setDelegate(new CastWindowManager.Delegate() { 88 @Override 89 public void onCreated() { 90 } 91 92 @Override 93 public void onClosed() { 94 mNativeCastWindow = 0; 95 mCastWindowManager.setDelegate(null); 96 finish(); 97 } 98 }); 99 setResolution(); 100 mCastWindowManager.setWindow(new WindowAndroid(this)); 101 102 registerBroadcastReceiver(); 103 104 String url = getIntent().getDataString(); 105 Log.d(TAG, "onCreate startupUrl: " + url); 106 mNativeCastWindow = mCastWindowManager.launchCastWindow(url); 107 108 getActiveContentViewCore().setContentViewClient(new ContentViewClient() { 109 @Override 110 public ContentVideoViewClient getContentVideoViewClient() { 111 return new ActivityContentVideoViewClient(CastShellActivity.this); 112 } 113 }); 114 } 115 116 @Override 117 protected void onDestroy() { 118 super.onDestroy(); 119 120 unregisterBroadcastReceiver(); 121 122 if (mNativeCastWindow != 0) { 123 mCastWindowManager.stopCastWindow(mNativeCastWindow, false /* gracefully */); 124 mNativeCastWindow = 0; 125 } 126 } 127 128 @Override 129 protected void onNewIntent(Intent intent) { 130 // Only handle direct intents (e.g. "fling") if this activity is also managing 131 // the browser process. 132 if (!shouldLaunchBrowser()) return; 133 134 String url = intent.getDataString(); 135 Log.d(TAG, "onNewIntent: " + url); 136 137 // Reset broadcast intent uri and receiver. 138 setIntent(intent); 139 exitIfUrlMissing(); 140 getActiveCastWindow().loadUrl(url); 141 } 142 143 @Override 144 protected void onResume() { 145 super.onResume(); 146 147 // Inform ContentView that this activity is being shown. 148 ContentViewCore view = getActiveContentViewCore(); 149 if (view != null) view.onShow(); 150 151 // Request audio focus so any other audio playback doesn't continue in the background. 152 if (mAudioManager.requestAudioFocus( 153 null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) 154 != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 155 Log.e(TAG, "Failed to obtain audio focus"); 156 } 157 } 158 159 @Override 160 protected void onPause() { 161 // As soon as the cast app is no longer in the foreground, we ought to immediately tear 162 // everything down. Apps should not continue running and playing sound in the background. 163 super.onPause(); 164 165 // Release the audio focus. Note that releasing audio focus does not stop audio playback, 166 // it just notifies the framework that this activity has stopped playing audio. 167 if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 168 Log.e(TAG, "Failed to abandon audio focus"); 169 } 170 171 ContentViewCore view = getActiveContentViewCore(); 172 if (view != null) view.onHide(); 173 174 finishGracefully(); 175 } 176 177 protected void finishGracefully() { 178 if (mNativeCastWindow != 0) { 179 mCastWindowManager.stopCastWindow(mNativeCastWindow, true /* gracefully */); 180 mNativeCastWindow = 0; 181 } 182 } 183 184 private void registerBroadcastReceiver() { 185 if (mBroadcastReceiver == null) { 186 mBroadcastReceiver = new BroadcastReceiver() { 187 @Override 188 public void onReceive(Context context, Intent intent) { 189 Log.d(TAG, "Received intent: action=" + intent.getAction()); 190 if (CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS.equals(intent.getAction())) { 191 mCastWindowManager.nativeEnableDevTools(true); 192 } else if (CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS.equals( 193 intent.getAction())) { 194 mCastWindowManager.nativeEnableDevTools(false); 195 } 196 } 197 }; 198 } 199 200 IntentFilter devtoolsBroadcastIntentFilter = new IntentFilter(); 201 devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS); 202 devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS); 203 LocalBroadcastManager.getInstance(this) 204 .registerReceiver(mBroadcastReceiver, devtoolsBroadcastIntentFilter); 205 } 206 207 private void unregisterBroadcastReceiver() { 208 LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this); 209 broadcastManager.unregisterReceiver(mBroadcastReceiver); 210 } 211 212 private void setResolution() { 213 int requestedHeight = getIntent().getIntExtra( 214 ACTION_EXTRA_RESOLUTION_HEIGHT, DEFAULT_HEIGHT_PIXELS); 215 int displayHeight = getResources().getDisplayMetrics().heightPixels; 216 // Clamp within [DEFAULT_HEIGHT_PIXELS, displayHeight] 217 int desiredHeight = 218 Math.min(displayHeight, Math.max(DEFAULT_HEIGHT_PIXELS, requestedHeight)); 219 double deviceScaleFactor = ((double) displayHeight) / desiredHeight; 220 Log.d(TAG, "Using scale factor " + deviceScaleFactor + " to set height " + desiredHeight); 221 CommandLine.getInstance().appendSwitchWithValue("force-device-scale-factor", 222 String.valueOf(deviceScaleFactor)); 223 } 224 225 private void exitIfUrlMissing() { 226 Intent intent = getIntent(); 227 if (intent != null && intent.getData() != null && !intent.getData().equals(Uri.EMPTY)) { 228 return; 229 } 230 // Log an exception so that the exit cause is obvious when reading the logs. 231 Log.e(TAG, "Activity will not start", 232 new IllegalArgumentException("Intent did not contain a valid url")); 233 System.exit(-1); 234 } 235 236 /** 237 * @return The currently visible {@link CastWindowAndroid} or null if one is not showing. 238 */ 239 public CastWindowAndroid getActiveCastWindow() { 240 return mCastWindowManager.getActiveCastWindow(); 241 } 242 243 /** 244 * @return The {@link ContentViewCore} owned by the currently visible {@link CastWindowAndroid}, 245 * or null if one is not showing. 246 */ 247 public ContentViewCore getActiveContentViewCore() { 248 CastWindowAndroid shell = getActiveCastWindow(); 249 return shell != null ? shell.getContentViewCore() : null; 250 } 251 252 @Override 253 public boolean onKeyUp(int keyCode, KeyEvent event) { 254 if (keyCode != KeyEvent.KEYCODE_BACK) { 255 return super.onKeyUp(keyCode, event); 256 } 257 258 // Just finish this activity to go back to the previous activity or launcher. 259 finishGracefully(); 260 return true; 261 } 262 263 @Override 264 public boolean dispatchKeyEvent(KeyEvent event) { 265 int keyCode = event.getKeyCode(); 266 if (keyCode == KeyEvent.KEYCODE_BACK) { 267 return super.dispatchKeyEvent(event); 268 } 269 return false; 270 } 271 272 @Override 273 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 274 return false; 275 } 276 277 @Override 278 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 279 return false; 280 } 281 282 @Override 283 public boolean dispatchTouchEvent(MotionEvent ev) { 284 return false; 285 } 286 287 @Override 288 public boolean dispatchTrackballEvent(MotionEvent ev) { 289 return false; 290 } 291} 292