Chromecast: initial checkin of Android-based cast shell.

[email protected],[email protected],[email protected]
BUG=400876

Review URL: https://codereview.chromium.org/490603002

Cr-Commit-Position: refs/heads/master@{#294476}
diff --git a/build/common.gypi b/build/common.gypi
index 7213449..fb9c697 100644
--- a/build/common.gypi
+++ b/build/common.gypi
@@ -96,7 +96,7 @@
               'use_aura%': 1,
             }],
 
-            ['chromecast==1', {
+            ['chromecast==1 and OS!="android"', {
               'embedded%': 1,
               'use_ozone%': 1,
             }],
diff --git a/chromecast/DEPS b/chromecast/DEPS
index 438dbc00..a10b957 100644
--- a/chromecast/DEPS
+++ b/chromecast/DEPS
@@ -5,6 +5,7 @@
   "+crypto",
   "+grit/chromecast_settings.h",
   "+grit/shell_resources.h",
+  "+jni",
   "+net",
   "+ui",
 ]
diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc
new file mode 100644
index 0000000..db79061
--- /dev/null
+++ b/chromecast/android/cast_jni_registrar.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/cast_jni_registrar.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "chromecast/android/chromecast_config_android.h"
+#include "chromecast/shell/browser/android/cast_window_android.h"
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+
+namespace chromecast {
+namespace android {
+
+namespace {
+
+static base::android::RegistrationMethod kMethods[] = {
+  { "CastWindowAndroid", shell::CastWindowAndroid::RegisterJni },
+  { "CastWindowManager", shell::RegisterCastWindowManager },
+};
+
+}  // namespace
+
+bool RegisterJni(JNIEnv* env) {
+  return RegisterNativeMethods(env, kMethods, arraysize(kMethods));
+}
+
+}  // namespace android
+}  // namespace chromecast
diff --git a/chromecast/android/cast_jni_registrar.h b/chromecast/android/cast_jni_registrar.h
new file mode 100644
index 0000000..b07316b
--- /dev/null
+++ b/chromecast/android/cast_jni_registrar.h
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
+#define CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
+
+#include <jni.h>
+
+namespace chromecast {
+namespace android {
+
+// Register all JNI bindings necessary for the Android cast shell.
+bool RegisterJni(JNIEnv* env);
+
+}  // namespace android
+}  // namespace chromecast
+
+#endif  // CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
diff --git a/chromecast/android/chromecast_config_android.cc b/chromecast/android/chromecast_config_android.cc
new file mode 100644
index 0000000..d698b70a
--- /dev/null
+++ b/chromecast/android/chromecast_config_android.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/chromecast_config_android.h"
+
+namespace chromecast {
+namespace android {
+
+namespace {
+base::LazyInstance<ChromecastConfigAndroid> g_instance =
+    LAZY_INSTANCE_INITIALIZER;
+}  // namespace
+
+// static
+ChromecastConfigAndroid* ChromecastConfigAndroid::GetInstance() {
+  return g_instance.Pointer();
+}
+
+ChromecastConfigAndroid::ChromecastConfigAndroid() {
+}
+
+ChromecastConfigAndroid::~ChromecastConfigAndroid() {
+}
+
+// Registers a handler to be notified when SendUsageStats is changed.
+void ChromecastConfigAndroid::SetSendUsageStatsChangedCallback(
+    const base::Callback<void(bool)>& callback) {
+  send_usage_stats_changed_callback_ = callback;
+}
+
+}  // namespace android
+}  // namespace chromecast
diff --git a/chromecast/android/chromecast_config_android.h b/chromecast/android/chromecast_config_android.h
new file mode 100644
index 0000000..627259e9
--- /dev/null
+++ b/chromecast/android/chromecast_config_android.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
+#define CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+
+namespace chromecast {
+namespace android {
+
+class ChromecastConfigAndroid {
+ public:
+  static ChromecastConfigAndroid* GetInstance();
+
+  // Registers a handler to be notified when SendUsageStats is changed.
+  void SetSendUsageStatsChangedCallback(
+      const base::Callback<void(bool)>& callback);
+
+  const base::Callback<void(bool)>& send_usage_stats_changed_callback() const {
+    return send_usage_stats_changed_callback_;
+  }
+
+ private:
+  friend struct base::DefaultLazyInstanceTraits<ChromecastConfigAndroid>;
+
+  ChromecastConfigAndroid();
+  ~ChromecastConfigAndroid();
+
+  base::Callback<void(bool)> send_usage_stats_changed_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromecastConfigAndroid);
+};
+
+}  // namespace android
+}  // namespace chromecast
+
+#endif  // CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
diff --git a/chromecast/android/platform_jni_loader.h b/chromecast/android/platform_jni_loader.h
new file mode 100644
index 0000000..76307461
--- /dev/null
+++ b/chromecast/android/platform_jni_loader.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
+#define CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
+
+#include <jni.h>
+
+namespace chromecast {
+namespace android {
+
+bool PlatformRegisterJni(JNIEnv* env);
+
+}  // namespace android
+}  // namespace chromecast
+
+#endif  // CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
diff --git a/chromecast/android/platform_jni_loader_stub.cc b/chromecast/android/platform_jni_loader_stub.cc
new file mode 100644
index 0000000..0f8d814
--- /dev/null
+++ b/chromecast/android/platform_jni_loader_stub.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/platform_jni_loader.h"
+
+namespace chromecast {
+namespace android {
+
+bool PlatformRegisterJni(JNIEnv* env) {
+  // Intentional no-op for public build.
+  return true;
+}
+
+}  // namespace android
+}  // namespace chromecast
diff --git a/chromecast/android/src/dummy b/chromecast/android/src/dummy
new file mode 100644
index 0000000..c1069e01
--- /dev/null
+++ b/chromecast/android/src/dummy
@@ -0,0 +1,2 @@
+Note(gunsch): This file is for the cast_shell_apk target in chromecast.gyp.
+See the notes above that target's 'java_in_dir' variable in chromecast.gyp.
diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp
index 55561df..ee35203 100644
--- a/chromecast/chromecast.gyp
+++ b/chromecast/chromecast.gyp
@@ -87,6 +87,16 @@
       ],
     },  # end of target 'cast_metrics_unittests'
     {
+      'target_name': 'cast_net',
+      'type': '<(component)',
+      'sources': [
+        'net/network_change_notifier_cast.cc',
+        'net/network_change_notifier_cast.h',
+        'net/network_change_notifier_factory_cast.cc',
+        'net/network_change_notifier_factory_cast.h',
+      ],
+    },
+    {
       'target_name': 'cast_service',
       'type': '<(component)',
       'dependencies': [
@@ -106,9 +116,18 @@
             '../base/base.gyp:base',
             '../content/content.gyp:content',
           ],
-          'sources': [
-            'service/cast_service_simple.cc',
-            'service/cast_service_simple.h',
+          'conditions': [
+            ['OS=="android"', {
+              'sources': [
+                'service/cast_service_android.cc',
+                'service/cast_service_android.h',
+              ],
+            }, {
+              'sources': [
+                'service/cast_service_simple.cc',
+                'service/cast_service_simple.h',
+              ],
+            }],
           ],
         }],
       ],
@@ -167,9 +186,11 @@
         },
       ],
     },
+    # This target contains all content-embedder implementation that is
+    # non-platform-specific.
     {
-      'target_name': 'cast_shell',
-      'type': 'executable',
+      'target_name': 'cast_shell_common',
+      'type': '<(component)',
       'dependencies': [
         'cast_common',
         'cast_metrics',
@@ -179,19 +200,12 @@
         'cast_version_header',
         'chromecast_locales.gyp:chromecast_locales_pak',
         'chromecast_locales.gyp:chromecast_settings',
-        'media/media.gyp:cast_media',
         '../components/components.gyp:component_metrics_proto',
         '../content/content.gyp:content',
         '../content/content.gyp:content_app_browser',
         '../skia/skia.gyp:skia',
-        '../ui/aura/aura.gyp:aura_test_support',
       ],
       'sources': [
-        'net/network_change_notifier_cast.cc',
-        'net/network_change_notifier_cast.h',
-        'net/network_change_notifier_factory_cast.cc',
-        'net/network_change_notifier_factory_cast.h',
-        'shell/app/cast_main.cc',
         'shell/app/cast_main_delegate.cc',
         'shell/app/cast_main_delegate.h',
         'shell/browser/cast_browser_context.cc',
@@ -221,13 +235,9 @@
       'conditions': [
         ['chromecast_branding=="Chrome"', {
           'dependencies': [
-            'internal/chromecast_internal.gyp:cast_gfx_internal',
             'internal/chromecast_internal.gyp:cast_shell_internal',
           ],
         }, {
-          'dependencies': [
-            '../ui/ozone/ozone.gyp:eglplatform_shim_x11',
-          ],
           'sources': [
             'shell/browser/devtools/remote_debugging_server_simple.cc',
             'shell/browser/webui/webui_cast_simple.cc',
@@ -275,4 +285,141 @@
       ],
     },
   ],  # end of targets
+
+  # Targets for Android receiver.
+  'conditions': [
+    ['OS=="android"', {
+      'targets': [
+        {
+          'target_name': 'libcast_shell_android',
+          'type': 'shared_library',
+          'dependencies': [
+            'cast_common',
+            'cast_jni_headers',
+            'cast_shell_common',
+            'cast_shell_pak',
+            'cast_version_header',
+            '../base/base.gyp:base',
+            '../content/content.gyp:content_app_browser',
+            '../content/content.gyp:content',
+            '../skia/skia.gyp:skia',
+            '../ui/gfx/gfx.gyp:gfx',
+            '../ui/gl/gl.gyp:gl',
+          ],
+          'sources': [
+            'android/cast_jni_registrar.cc',
+            'android/cast_jni_registrar.h',
+            'android/chromecast_config_android.cc',
+            'android/chromecast_config_android.h',
+            'android/platform_jni_loader.h',
+            'shell/app/android/cast_jni_loader.cc',
+            'shell/browser/android/cast_window_manager.cc',
+            'shell/browser/android/cast_window_manager.h',
+            'shell/browser/android/cast_window_android.cc',
+            'shell/browser/android/cast_window_android.h',
+          ],
+          'conditions': [
+            ['chromecast_branding=="Chrome"', {
+              'dependencies': [
+                '<(cast_internal_gyp):cast_shell_android_internal'
+              ],
+            }, {
+              'sources': [
+                'android/platform_jni_loader_stub.cc',
+              ],
+            }]
+          ],
+        },  # end of target 'libcast_shell_android'
+        {
+          'target_name': 'cast_shell_java',
+          'type': 'none',
+          'dependencies': [
+            '../base/base.gyp:base_java',
+            '../content/content.gyp:content_java',
+            '../media/media.gyp:media_java',
+            '../net/net.gyp:net_java',
+            '../third_party/android_tools/android_tools.gyp:android_support_v13_javalib',
+            '../ui/android/ui_android.gyp:ui_java',
+          ],
+          'variables': {
+            'has_java_resources': 1,
+            'java_in_dir': 'shell/android/apk',
+            'resource_dir': 'shell/android/apk/res',
+            'R_package': 'org.chromium.chromecast.shell',
+          },
+          'includes': ['../build/java.gypi'],
+        },  # end of target 'cast_shell_java'
+        {
+          'target_name': 'cast_shell_apk',
+          'type': 'none',
+          'dependencies': [
+            'cast_shell_java',
+            'libcast_shell_android',
+          ],
+          'variables': {
+            'apk_name': 'CastShell',
+            'manifest_package_name': 'org.chromium.chromecast.shell',
+            # Note(gunsch): there are no Java files in the android/ directory.
+            # Unfortunately, the java_apk.gypi target rigidly insists on having
+            # a java_in_dir directory, but complains about duplicate classes
+            # from the common cast_shell_java target (shared with internal APK)
+            # if the actual Java path is used.
+            # This will hopefully be removable after the great GN migration.
+            'java_in_dir': 'android',
+            'android_manifest_path': 'shell/android/apk/AndroidManifest.xml',
+            'package_name': 'org.chromium.chromecast.shell',
+            'native_lib_target': 'libcast_shell_android',
+            'asset_location': '<(PRODUCT_DIR)/assets',
+            'additional_input_paths': ['<(PRODUCT_DIR)/assets/cast_shell.pak'],
+          },
+          'includes': [ '../build/java_apk.gypi' ],
+        },
+        {
+          'target_name': 'cast_jni_headers',
+          'type': 'none',
+          'sources': [
+            'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java',
+            'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java',
+          ],
+          'direct_dependent_settings': {
+            'include_dirs': [
+              '<(SHARED_INTERMEDIATE_DIR)/chromecast',
+            ],
+          },
+          'variables': {
+            'jni_gen_package': 'chromecast',
+          },
+          'includes': [ '../build/jni_generator.gypi' ],
+        },
+      ],  # end of targets
+    }, {  # OS != "android"
+      'targets': [
+        # This target includes all dependencies that cannot be built on Android.
+        {
+          'target_name': 'cast_shell',
+          'type': 'executable',
+          'dependencies': [
+            'cast_net',
+            'cast_shell_common',
+            'media/media.gyp:cast_media',
+            '../ui/aura/aura.gyp:aura_test_support',
+          ],
+          'sources': [
+            'shell/app/cast_main.cc',
+          ],
+          'conditions': [
+            ['chromecast_branding=="Chrome"', {
+              'dependencies': [
+                'internal/chromecast_internal.gyp:cast_gfx_internal',
+              ],
+            }, {
+              'dependencies': [
+                '../ui/ozone/ozone.gyp:eglplatform_shim_x11',
+              ],
+            }],
+          ],
+        },
+      ],  # end of targets
+    }],
+  ],  # end of conditions
 }
diff --git a/chromecast/common/cast_paths.cc b/chromecast/common/cast_paths.cc
index fb3cdf1d..d5d471ce 100644
--- a/chromecast/common/cast_paths.cc
+++ b/chromecast/common/cast_paths.cc
@@ -7,6 +7,7 @@
 #include "base/base_paths.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/logging.h"
 #include "base/path_service.h"
 #include "build/build_config.h"
 
@@ -27,10 +28,18 @@
 #endif
       return true;
     }
+#if defined(OS_ANDROID)
+    case FILE_CAST_ANDROID_LOG: {
+      base::FilePath base_dir;
+      CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &base_dir));
+      *result = base_dir.AppendASCII("cast_shell.log");
+      return true;
+    }
+#endif  // defined(OS_ANDROID)
     case FILE_CAST_CONFIG: {
       base::FilePath data_dir;
 #if defined(OS_ANDROID)
-      CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir);
+      CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir));
       *result = data_dir.Append("cast_shell.conf");
 #else
       CHECK(PathService::Get(DIR_CAST_HOME, &data_dir));
diff --git a/chromecast/common/cast_paths.h b/chromecast/common/cast_paths.h
index f228cd185..5ab156c20 100644
--- a/chromecast/common/cast_paths.h
+++ b/chromecast/common/cast_paths.h
@@ -5,6 +5,8 @@
 #ifndef CHROMECAST_COMMON_CAST_PATHS_H_
 #define CHROMECAST_COMMON_CAST_PATHS_H_
 
+#include "build/build_config.h"
+
 // This file declares path keys for the chromecast module.  These can be used
 // with the PathService to access various special directories and files.
 
@@ -16,6 +18,9 @@
   DIR_CAST_HOME,    // Return a modified $HOME which works for both
                     // development use and the actual device.
 
+#if defined(OS_ANDROID)
+  FILE_CAST_ANDROID_LOG, // Log file location for Android.
+#endif  // defined(OS_ANDROID)
   FILE_CAST_CONFIG, // Config/preferences file path.
   FILE_CAST_PAK,    // cast_shell.pak file path.
   PATH_END
diff --git a/chromecast/common/global_descriptors.h b/chromecast/common/global_descriptors.h
new file mode 100644
index 0000000..cbbbb2c
--- /dev/null
+++ b/chromecast/common/global_descriptors.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
+#define CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
+
+#include "content/public/common/content_descriptors.h"
+
+// This is a list of global descriptor keys to be used with the
+// base::GlobalDescriptors object (see base/posix/global_descriptors.h)
+enum {
+  // TODO(gunsch): Remove once there's a real value here. Otherwise, non-Android
+  // build compile fails due to empty enum.
+  kDummyValue = kContentIPCDescriptorMax + 1,
+#if defined(OS_ANDROID)
+  kAndroidPakDescriptor,
+#endif  // defined(OS_ANDROID)
+};
+
+#endif  // CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
diff --git a/chromecast/service/cast_service_android.cc b/chromecast/service/cast_service_android.cc
new file mode 100644
index 0000000..afaab64
--- /dev/null
+++ b/chromecast/service/cast_service_android.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/service/cast_service_android.h"
+
+#include "base/callback.h"
+#include "chromecast/android/chromecast_config_android.h"
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+
+namespace chromecast {
+
+// static
+CastService* CastService::Create(content::BrowserContext* browser_context) {
+  return new CastServiceAndroid(browser_context);
+}
+
+CastServiceAndroid::CastServiceAndroid(content::BrowserContext* browser_context)
+    : CastService(browser_context) {
+  shell::SetBrowserContextAndroid(browser_context);
+}
+
+CastServiceAndroid::~CastServiceAndroid() {
+}
+
+void CastServiceAndroid::Initialize() {
+  // TODO(gunsch): Wire this the SendUsageStatsChanged callback once
+  // CastService::Delegate is added.
+}
+
+void CastServiceAndroid::StartInternal() {
+}
+
+void CastServiceAndroid::StopInternal() {
+}
+
+}  // namespace chromecast
diff --git a/chromecast/service/cast_service_android.h b/chromecast/service/cast_service_android.h
new file mode 100644
index 0000000..234b012
--- /dev/null
+++ b/chromecast/service/cast_service_android.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
+#define CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
+
+#include "base/macros.h"
+#include "chromecast/service/cast_service.h"
+
+namespace chromecast {
+
+class CastServiceAndroid : public CastService {
+ public:
+  explicit CastServiceAndroid(content::BrowserContext* browser_context);
+  virtual ~CastServiceAndroid();
+
+ protected:
+  // CastService implementation.
+  virtual void Initialize() OVERRIDE;
+  virtual void StartInternal() OVERRIDE;
+  virtual void StopInternal() OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CastServiceAndroid);
+};
+
+}  // namespace chromecast
+
+#endif  // CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
diff --git a/chromecast/shell/android/DEPS b/chromecast/shell/android/DEPS
new file mode 100644
index 0000000..69269bc
--- /dev/null
+++ b/chromecast/shell/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/android",
+]
diff --git a/chromecast/shell/android/apk/AndroidManifest.xml b/chromecast/shell/android/apk/AndroidManifest.xml
new file mode 100644
index 0000000..63ad17c
--- /dev/null
+++ b/chromecast/shell/android/apk/AndroidManifest.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.chromium.chromecast.shell">
+
+    <permission android:name="org.chromium.chromecast.shell.permission.SANDBOX"
+                android:protectionLevel="signature" />
+
+    <application android:name="org.chromium.chromecast.shell.CastApplication"
+                 android:icon="@mipmap/app_icon"
+                 android:label="@string/app_name">
+        <activity android:name="org.chromium.chromecast.shell.CastShellActivity"
+                  android:theme="@style/CastShellTheme"
+                  android:exported="true"
+                  android:hardwareAccelerated="true"
+                  android:taskAffinity=".CastShellActivity"
+                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
+                  android:excludeFromRecents="true"
+                  android:noHistory="true"
+                  android:permission="android.permission.INTERNET">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <!-- The following service entries exist in order to allow us to
+             start more than one sandboxed process. -->
+        <service android:name="org.chromium.content.app.SandboxedProcessService0"
+                 android:process=":sandboxed_process0"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService1"
+                 android:process=":sandboxed_process1"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService2"
+                 android:process=":sandboxed_process2"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService3"
+                 android:process=":sandboxed_process3"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService4"
+                 android:process=":sandboxed_process4"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService5"
+                 android:process=":sandboxed_process5"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService6"
+                 android:process=":sandboxed_process6"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService7"
+                 android:process=":sandboxed_process7"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService8"
+                 android:process=":sandboxed_process8"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService9"
+                 android:process=":sandboxed_process9"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService10"
+                 android:process=":sandboxed_process10"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService11"
+                 android:process=":sandboxed_process11"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService12"
+                 android:process=":sandboxed_process12"
+                 android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService13"
+                 android:process=":sandboxed_process13"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService14"
+                 android:process=":sandboxed_process14"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService15"
+                 android:process=":sandboxed_process15"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService16"
+                 android:process=":sandboxed_process16"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService17"
+                 android:process=":sandboxed_process17"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService18"
+                 android:process=":sandboxed_process18"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        <service android:name="org.chromium.content.app.SandboxedProcessService19"
+                 android:process=":sandboxed_process19"
+                 android:permission="org.chromium.content_shell.permission.SANDBOX"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+    </application>
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+</manifest>
diff --git a/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml
new file mode 100644
index 0000000..428b1a51
--- /dev/null
+++ b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <org.chromium.chromecast.shell.CastWindowManager
+        android:id="@+id/shell_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</merge>
diff --git a/chromecast/shell/android/apk/res/layout/cast_window_view.xml b/chromecast/shell/android/apk/res/layout/cast_window_view.xml
new file mode 100644
index 0000000..ce57c9e
--- /dev/null
+++ b/chromecast/shell/android/apk/res/layout/cast_window_view.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+ -->
+
+<org.chromium.chromecast.shell.CastWindowAndroid
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <FrameLayout android:id="@+id/contentview_holder"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</org.chromium.chromecast.shell.CastWindowAndroid>
diff --git a/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png
new file mode 100644
index 0000000..72d8318
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png
new file mode 100644
index 0000000..4076d5d
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png
new file mode 100644
index 0000000..c7f092c
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png
new file mode 100644
index 0000000..9addc3b
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/values-v17/styles.xml b/chromecast/shell/android/apk/res/values-v17/styles.xml
new file mode 100644
index 0000000..80eaf9b
--- /dev/null
+++ b/chromecast/shell/android/apk/res/values-v17/styles.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+
+<resources>
+    <style name="CastShellTheme" parent="@android:style/Theme.Holo.Light.NoActionBar">
+        <item name="android:windowBackground">@android:color/black</item>
+    </style>
+</resources>
diff --git a/chromecast/shell/android/apk/res/values/strings.xml b/chromecast/shell/android/apk/res/values/strings.xml
new file mode 100644
index 0000000..9ca510a6
--- /dev/null
+++ b/chromecast/shell/android/apk/res/values/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <string name="app_name">Chromecast Android Shell</string>
+    <string name="browser_process_initialization_failed">Initialization failed.</string>
+</resources>
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
new file mode 100644
index 0000000..1360dab
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import org.chromium.base.PathUtils;
+import org.chromium.content.app.ContentApplication;
+import org.chromium.content.browser.ResourceExtractor;
+
+/**
+ * Entry point for the Android cast shell application.  Handles initialization of information that
+ * needs to be shared across the main activity and the child services created.
+ *
+ * Note that this gets run for each process, including sandboxed child render processes. Child
+ * processes don't need most of the full "setup" performed in CastBrowserHelper.java, but they do
+ * require a few basic pieces (found here).
+ */
+public class CastApplication extends ContentApplication {
+
+    private static final String[] MANDATORY_PAK_FILES = new String[] {"cast_shell.pak"};
+    private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cast_shell";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        initializeApplicationParameters();
+    }
+
+    public static void initializeApplicationParameters() {
+        ResourceExtractor.setMandatoryPaksToExtract(MANDATORY_PAK_FILES);
+        PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+    }
+
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
new file mode 100644
index 0000000..d3ea8ea
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Debug;
+import android.util.Log;
+
+import org.chromium.base.BaseSwitches;
+import org.chromium.base.CommandLine;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.content.browser.BrowserStartupController;
+import org.chromium.content.browser.DeviceUtils;
+import org.chromium.content.common.ContentSwitches;
+
+/**
+ * Static, one-time initialization for the browser process.
+ */
+public class CastBrowserHelper {
+    private static final String TAG = "CastBrowserHelper";
+
+    public static final String COMMAND_LINE_FILE = "/data/local/tmp/castshell-command-line";
+    public static final String COMMAND_LINE_ARGS_KEY = "commandLineArgs";
+
+    private static boolean sIsBrowserInitialized = false;
+
+    /**
+     * Starts the browser process synchronously, returning success or failure. If the browser has
+     * already started, immediately returns true without performing any more initialization.
+     * This may only be called on the UI thread.
+     *
+     * @return whether or not the process started successfully
+     */
+    public static boolean initializeBrowser(Context context) {
+        if (sIsBrowserInitialized) return true;
+
+        Log.d(TAG, "Performing one-time browser initialization");
+
+        // Initializing the command line must occur before loading the library.
+        if (!CommandLine.isInitialized()) {
+            if (allowCommandLineImport()) {
+                Log.d(TAG, "Initializing command line from " + COMMAND_LINE_FILE);
+                CommandLine.initFromFile(COMMAND_LINE_FILE);
+            } else {
+                CommandLine.init(null);
+            }
+
+            if (context instanceof Activity) {
+                Intent launchingIntent = ((Activity) context).getIntent();
+                String[] commandLineParams = getCommandLineParamsFromIntent(launchingIntent);
+                if (commandLineParams != null) {
+                    CommandLine.getInstance().appendSwitchesAndArguments(commandLineParams);
+                }
+            }
+        }
+
+        CommandLine.getInstance().appendSwitchWithValue(
+                ContentSwitches.FORCE_DEVICE_SCALE_FACTOR, "1");
+
+        waitForDebuggerIfNeeded();
+
+        DeviceUtils.addDeviceSpecificUserAgentSwitch(context);
+
+        try {
+            LibraryLoader.ensureInitialized();
+
+            Log.d(TAG, "Loading BrowserStartupController...");
+            BrowserStartupController.get(context).startBrowserProcessesSync(false);
+
+            sIsBrowserInitialized = true;
+            return true;
+        } catch (ProcessInitException e) {
+            Log.e(TAG, "Unable to launch browser process.", e);
+            return false;
+        }
+    }
+
+    private static boolean allowCommandLineImport() {
+      return !Build.TYPE.equals("user");
+    }
+
+    private static String[] getCommandLineParamsFromIntent(Intent intent) {
+        return intent != null ? intent.getStringArrayExtra(COMMAND_LINE_ARGS_KEY) : null;
+    }
+
+    private static void waitForDebuggerIfNeeded() {
+        if (!CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) {
+            return;
+        }
+        Log.e(TAG, "Waiting for Java debugger to connect...");
+        Debug.waitForDebugger();
+        Log.e(TAG, "Java debugger connected. Resuming execution.");
+    }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java
new file mode 100644
index 0000000..e88bad3
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java
@@ -0,0 +1,291 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import org.chromium.base.CommandLine;
+import org.chromium.content.browser.ActivityContentVideoViewClient;
+import org.chromium.content.browser.ContentVideoViewClient;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Activity for managing the Cast shell.
+ */
+public class CastShellActivity extends Activity {
+    private static final String TAG = "CastShellActivity";
+
+    private static final String ACTIVE_SHELL_URL_KEY = "activeUrl";
+    private static final int DEFAULT_HEIGHT_PIXELS = 720;
+    public static final String ACTION_EXTRA_RESOLUTION_HEIGHT =
+        "org.chromium.chromecast.shell.intent.extra.RESOLUTION_HEIGHT";
+
+    private CastWindowManager mCastWindowManager;
+    private AudioManager mAudioManager;
+    private BroadcastReceiver mBroadcastReceiver;
+
+    // Native window instance.
+    // TODO(byungchul, gunsch): CastShellActivity, CastWindowAndroid, and native CastWindowAndroid
+    // have a one-to-one relationship. Consider instantiating CastWindow here and CastWindow having
+    // this native shell instance.
+    private long mNativeCastWindow;
+
+    /**
+     * Returns whether or not CastShellActivity should launch the browser startup sequence.
+     * Intended to be overridden.
+     */
+    protected boolean shouldLaunchBrowser() {
+        return true;
+    }
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        exitIfUrlMissing();
+
+        if (shouldLaunchBrowser()) {
+            if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) {
+                Toast.makeText(this,
+                        R.string.browser_process_initialization_failed,
+                        Toast.LENGTH_SHORT).show();
+                finish();
+            }
+        }
+
+        // Whenever our app is visible, volume controls should modify the music stream.
+        // For more information read:
+        // http://developer.android.com/training/managing-audio/volume-playback.html
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        // Set flags to both exit sleep mode when this activity starts and
+        // avoid entering sleep mode while playing media. We cannot distinguish
+        // between video and audio so this applies to both.
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
+
+        setContentView(R.layout.cast_shell_activity);
+        mCastWindowManager = (CastWindowManager) findViewById(R.id.shell_container);
+        mCastWindowManager.setDelegate(new CastWindowManager.Delegate() {
+                @Override
+                public void onCreated() {
+                }
+
+                @Override
+                public void onClosed() {
+                    mNativeCastWindow = 0;
+                    mCastWindowManager.setDelegate(null);
+                    finish();
+                }
+            });
+        setResolution();
+        mCastWindowManager.setWindow(new WindowAndroid(this));
+
+        registerBroadcastReceiver();
+
+        String url = getIntent().getDataString();
+        Log.d(TAG, "onCreate startupUrl: " + url);
+        mNativeCastWindow = mCastWindowManager.launchCastWindow(url);
+
+        getActiveContentViewCore().setContentViewClient(new ContentViewClient() {
+            @Override
+            public ContentVideoViewClient getContentVideoViewClient() {
+                return new ActivityContentVideoViewClient(CastShellActivity.this);
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterBroadcastReceiver();
+
+        if (mNativeCastWindow != 0) {
+            mCastWindowManager.stopCastWindow(mNativeCastWindow);
+            mNativeCastWindow = 0;
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        // Only handle direct intents (e.g. "fling") if this activity is also managing
+        // the browser process.
+        if (!shouldLaunchBrowser()) return;
+
+        String url = intent.getDataString();
+        Log.d(TAG, "onNewIntent: " + url);
+
+        // Reset broadcast intent uri and receiver.
+        setIntent(intent);
+        exitIfUrlMissing();
+        getActiveCastWindow().loadUrl(url);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Inform ContentView that this activity is being shown.
+        ContentViewCore view = getActiveContentViewCore();
+        if (view != null) view.onShow();
+
+        // Request audio focus so any other audio playback doesn't continue in the background.
+        if (mAudioManager.requestAudioFocus(
+                null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
+                != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+            Log.e(TAG, "Failed to obtain audio focus");
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        // As soon as the cast app is no longer in the foreground, we ought to immediately tear
+        // everything down. Apps should not continue running and playing sound in the background.
+        super.onPause();
+
+        // Release the audio focus. Note that releasing audio focus does not stop audio playback,
+        // it just notifies the framework that this activity has stopped playing audio.
+        if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+            Log.e(TAG, "Failed to abandon audio focus");
+        }
+
+        ContentViewCore view = getActiveContentViewCore();
+        if (view != null) view.onHide();
+
+        finishGracefully();
+    }
+
+    protected void finishGracefully() {
+        if (mNativeCastWindow != 0) {
+            mCastWindowManager.stopCastWindow(mNativeCastWindow);
+            mNativeCastWindow = 0;
+        }
+    }
+
+    private void registerBroadcastReceiver() {
+        if (mBroadcastReceiver == null) {
+            mBroadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    Log.d(TAG, "Received intent: action=" + intent.getAction());
+                    if (CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS.equals(intent.getAction())) {
+                        mCastWindowManager.nativeEnableDevTools(true);
+                    } else if (CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS.equals(
+                            intent.getAction())) {
+                        mCastWindowManager.nativeEnableDevTools(false);
+                    }
+                }
+            };
+        }
+
+        IntentFilter devtoolsBroadcastIntentFilter = new IntentFilter();
+        devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS);
+        devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS);
+        LocalBroadcastManager.getInstance(this)
+                .registerReceiver(mBroadcastReceiver, devtoolsBroadcastIntentFilter);
+    }
+
+    private void unregisterBroadcastReceiver() {
+        LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
+        broadcastManager.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    private void setResolution() {
+        int requestedHeight = getIntent().getIntExtra(
+                ACTION_EXTRA_RESOLUTION_HEIGHT, DEFAULT_HEIGHT_PIXELS);
+        int displayHeight = getResources().getDisplayMetrics().heightPixels;
+        // Clamp within [DEFAULT_HEIGHT_PIXELS, displayHeight]
+        int desiredHeight =
+                Math.min(displayHeight, Math.max(DEFAULT_HEIGHT_PIXELS, requestedHeight));
+        double deviceScaleFactor = ((double)displayHeight) / desiredHeight;
+        Log.d(TAG, "Using scale factor " + deviceScaleFactor + " to set height " + desiredHeight);
+        CommandLine.getInstance().appendSwitchWithValue("force-device-scale-factor",
+                String.valueOf(deviceScaleFactor));
+    }
+
+    private void exitIfUrlMissing() {
+        Intent intent = getIntent();
+        if (intent != null && intent.getData() != null && !intent.getData().equals(Uri.EMPTY)) {
+            return;
+        }
+        // Log an exception so that the exit cause is obvious when reading the logs.
+        Log.e(TAG, "Activity will not start",
+            new IllegalArgumentException("Intent did not contain a valid url"));
+        System.exit(-1);
+    }
+
+    /**
+     * @return The currently visible {@link CastWindowAndroid} or null if one is not showing.
+     */
+    public CastWindowAndroid getActiveCastWindow() {
+        return mCastWindowManager.getActiveCastWindow();
+    }
+
+    /**
+     * @return The {@link ContentViewCore} owned by the currently visible {@link CastWindowAndroid},
+     *         or null if one is not showing.
+     */
+    public ContentViewCore getActiveContentViewCore() {
+        CastWindowAndroid shell = getActiveCastWindow();
+        return shell != null ? shell.getContentViewCore() : null;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode != KeyEvent.KEYCODE_BACK) {
+            return super.onKeyUp(keyCode, event);
+        }
+
+        // Just finish this activity to go back to the previous activity or launcher.
+        finishGracefully();
+        return true;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return super.dispatchKeyEvent(event);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        return false;
+    }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java
new file mode 100644
index 0000000..057f812
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.content.browser.ContentView;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewRenderView;
+import org.chromium.content.browser.WebContentsObserverAndroid;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Container for the various UI components that make up a shell window.
+ */
+@JNINamespace("chromecast::shell")
+public class CastWindowAndroid extends LinearLayout {
+    public static final String TAG = "CastWindowAndroid";
+
+    public static final String ACTION_PAGE_LOADED = "castPageLoaded";
+    public static final String ACTION_ENABLE_DEV_TOOLS = "castEnableDevTools";
+    public static final String ACTION_DISABLE_DEV_TOOLS = "castDisableDevTools";
+
+    private ContentViewCore mContentViewCore;
+    private ContentViewRenderView mContentViewRenderView;
+    private WebContentsObserverAndroid mWebContentsObserver;
+    private WindowAndroid mWindow;
+
+    /**
+     * Constructor for inflating via XML.
+     */
+    public CastWindowAndroid(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Set the SurfaceView being renderered to as soon as it is available.
+     */
+    public void setContentViewRenderView(ContentViewRenderView contentViewRenderView) {
+        FrameLayout contentViewHolder = (FrameLayout) findViewById(R.id.contentview_holder);
+        if (contentViewRenderView == null) {
+            if (mContentViewRenderView != null) {
+                contentViewHolder.removeView(mContentViewRenderView);
+            }
+        } else {
+            contentViewHolder.addView(contentViewRenderView,
+                    new FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.MATCH_PARENT));
+        }
+        mContentViewRenderView = contentViewRenderView;
+    }
+
+    /**
+     * @param window The owning window for this shell.
+     */
+    public void setWindow(WindowAndroid window) {
+        mWindow = window;
+    }
+
+    /**
+     * Loads an URL.  This will perform minimal amounts of sanitizing of the URL to attempt to
+     * make it valid.
+     *
+     * @param url The URL to be loaded by the shell.
+     */
+    public void loadUrl(String url) {
+        if (url == null) return;
+
+        if (TextUtils.equals(url, mContentViewCore.getUrl())) {
+            mContentViewCore.reload(true);
+        } else {
+            mContentViewCore.loadUrl(new LoadUrlParams(normalizeUrl(url)));
+        }
+
+        // TODO(aurimas): Remove this when crbug.com/174541 is fixed.
+        mContentViewCore.getContainerView().clearFocus();
+        mContentViewCore.getContainerView().requestFocus();
+    }
+
+    /**
+     * Given a URI String, performs minimal normalization to attempt to build a usable URL from it.
+     * @param uriString The passed-in path to be normalized.
+     * @return The normalized URL, as a string.
+     */
+    private static String normalizeUrl(String uriString) {
+        if (uriString == null) return uriString;
+        Uri uri = Uri.parse(uriString);
+        if (uri.getScheme() == null) {
+            uri = Uri.parse("http://" + uriString);
+        }
+        return uri.toString();
+    }
+
+    /**
+     * Initializes the ContentView based on the native tab contents pointer passed in.
+     * @param nativeWebContents The pointer to the native tab contents object.
+     */
+    @SuppressWarnings("unused")
+    @CalledByNative
+    private void initFromNativeWebContents(long nativeWebContents) {
+        Context context = getContext();
+        mContentViewCore = new ContentViewCore(context);
+        ContentView view = ContentView.newInstance(context, mContentViewCore);
+        mContentViewCore.initialize(view, view, nativeWebContents, mWindow);
+
+        if (getParent() != null) mContentViewCore.onShow();
+        ((FrameLayout) findViewById(R.id.contentview_holder)).addView(view,
+                new FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.MATCH_PARENT));
+        view.requestFocus();
+        mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
+
+        mWebContentsObserver = new WebContentsObserverAndroid(mContentViewCore.getWebContents()) {
+            @Override
+            public void didStopLoading(String url) {
+                Uri intentUri = Uri.parse(mContentViewCore
+                        .getOriginalUrlForActiveNavigationEntry());
+                Log.v(TAG, "Broadcast ACTION_PAGE_LOADED: scheme=" + intentUri.getScheme()
+                        + ", host=" + intentUri.getHost());
+                LocalBroadcastManager.getInstance(getContext()).sendBroadcast(
+                        new Intent(ACTION_PAGE_LOADED, intentUri));
+            }
+        };
+    }
+
+    /**
+     * @return The {@link ViewGroup} currently shown by this Shell.
+     */
+    public ViewGroup getContentView() {
+        return mContentViewCore.getContainerView();
+    }
+
+    /**
+     * @return The {@link ContentViewCore} currently managing the view shown by this Shell.
+     */
+    public ContentViewCore getContentViewCore() {
+        return mContentViewCore;
+    }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java
new file mode 100644
index 0000000..a8aca376
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewRenderView;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Container and generator of CastWindow instances.
+ */
+@JNINamespace("chromecast::shell")
+public class CastWindowManager extends FrameLayout {
+    private static final String TAG = "CastWindowManager";
+
+    private WindowAndroid mWindow;
+    private CastWindowAndroid mActiveCastWindow;
+
+    // The target for all content rendering.
+    private ContentViewRenderView mContentViewRenderView;
+
+    /**
+     * Delegate to deliver events from the native window.
+     */
+    public interface Delegate {
+        public void onCreated();
+        public void onClosed();
+    }
+    private Delegate mDelegate;
+
+    /**
+     * Constructor for inflating via XML.
+     */
+    public CastWindowManager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        nativeInit(this);
+    }
+
+    /**
+     * @param delegate Delegate to handle events.
+     */
+    public void setDelegate(Delegate delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * @param window Represents the activity window.
+     */
+    public void setWindow(WindowAndroid window) {
+        assert window != null;
+        mWindow = window;
+        mContentViewRenderView = new ContentViewRenderView(getContext()) {
+            @Override
+            protected void onReadyToRender() {
+                setOverlayVideoMode(true);
+            }
+        };
+        mContentViewRenderView.onNativeLibraryLoaded(window);
+        // Setting the background color to black avoids rendering a white splash screen
+        // before the players are loaded. See crbug/307113 for details.
+        mContentViewRenderView.setSurfaceViewBackgroundColor(Color.BLACK);
+    }
+
+    /**
+     * @return The window used to generate all shells.
+     */
+    public WindowAndroid getWindow() {
+        return mWindow;
+    }
+
+    /**
+     * @return The currently visible shell view or null if one is not showing.
+     */
+    public CastWindowAndroid getActiveCastWindow() {
+        return mActiveCastWindow;
+    }
+
+    /**
+     * Creates a new shell pointing to the specified URL.
+     * @param url The URL the shell should load upon creation.
+     * @return Pointer of native cast shell instance.
+     */
+    public long launchCastWindow(String url) {
+        return nativeLaunchCastWindow(url);
+    }
+
+    /**
+     * Stops a native cast shell instance created by {@link #launchCastWindow(String)}.
+     * @param nativeCastWindow Pointer of native cast shell instance returned
+     *        by {@link #launchCastWindow(String)}.
+     * @see #launchCastWindow(String)
+     */
+    public void stopCastWindow(long nativeCastWindow) {
+        nativeStopCastWindow(nativeCastWindow);
+    }
+
+    @SuppressWarnings("unused")
+    @CalledByNative
+    private Object createCastWindow() {
+        assert mContentViewRenderView != null;
+        LayoutInflater inflater =
+                (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        CastWindowAndroid shellView =
+                (CastWindowAndroid) inflater.inflate(R.layout.cast_window_view, null);
+        shellView.setWindow(mWindow);
+
+        if (mActiveCastWindow != null) closeCastWindow(mActiveCastWindow);
+
+        shellView.setContentViewRenderView(mContentViewRenderView);
+        addView(shellView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+        mActiveCastWindow = shellView;
+        ContentViewCore contentViewCore = mActiveCastWindow.getContentViewCore();
+        if (contentViewCore != null) {
+            mContentViewRenderView.setCurrentContentViewCore(contentViewCore);
+            contentViewCore.onShow();
+        }
+
+        if (mDelegate != null) {
+            mDelegate.onCreated();
+        }
+
+        return shellView;
+    }
+
+    @SuppressWarnings("unused")
+    @CalledByNative
+    private void closeCastWindow(CastWindowAndroid shellView) {
+        if (shellView == mActiveCastWindow) mActiveCastWindow = null;
+        ContentViewCore contentViewCore = shellView.getContentViewCore();
+        if (contentViewCore != null) contentViewCore.onHide();
+        shellView.setContentViewRenderView(null);
+        shellView.setWindow(null);
+        removeView(shellView);
+
+        if (mDelegate != null) {
+            mDelegate.onClosed();
+        }
+    }
+
+    private static native void nativeInit(Object shellManagerInstance);
+    private static native long nativeLaunchCastWindow(String url);
+    private static native void nativeStopCastWindow(long pointerOfNativeCastWindow);
+    public static native void nativeEnableDevTools(boolean enable);
+}
diff --git a/chromecast/shell/app/DEPS b/chromecast/shell/app/DEPS
index 7c1793e..efc610d 100644
--- a/chromecast/shell/app/DEPS
+++ b/chromecast/shell/app/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+content/public/app",
+  "+content/public/browser",
 ]
diff --git a/chromecast/shell/app/android/cast_jni_loader.cc b/chromecast/shell/app/android/cast_jni_loader.cc
new file mode 100644
index 0000000..4cbf8f8
--- /dev/null
+++ b/chromecast/shell/app/android/cast_jni_loader.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "base/basictypes.h"
+#include "base/debug/debugger.h"
+#include "base/logging.h"
+#include "chromecast/android/cast_jni_registrar.h"
+#include "chromecast/android/platform_jni_loader.h"
+#include "chromecast/shell/app/cast_main_delegate.h"
+#include "content/public/app/android_library_loader_hooks.h"
+#include "content/public/app/content_main.h"
+#include "content/public/browser/android/compositor.h"
+
+// This is called by the VM when the shared library is first loaded.
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  base::android::SetLibraryLoadedHook(&content::LibraryLoaded);
+  base::android::InitVM(vm);
+  JNIEnv* env = base::android::AttachCurrentThread();
+
+  if (!base::android::RegisterLibraryLoaderEntryHook(env)) return -1;
+
+  // To be called only from the UI thread.  If loading the library is done on
+  // a separate thread, this should be moved elsewhere.
+  if (!chromecast::android::RegisterJni(env)) return -1;
+  // Allow platform-specific implementations to perform more JNI registration.
+  if (!chromecast::android::PlatformRegisterJni(env)) return -1;
+
+  content::Compositor::Initialize();
+  content::SetContentMainDelegate(new chromecast::shell::CastMainDelegate);
+
+  return JNI_VERSION_1_4;
+}
diff --git a/chromecast/shell/app/cast_main_delegate.cc b/chromecast/shell/app/cast_main_delegate.cc
index 1fb2ad77..f713f5b 100644
--- a/chromecast/shell/app/cast_main_delegate.cc
+++ b/chromecast/shell/app/cast_main_delegate.cc
@@ -4,12 +4,16 @@
 
 #include "chromecast/shell/app/cast_main_delegate.h"
 
+#include "base/cpu.h"
 #include "base/logging.h"
 #include "base/path_service.h"
+#include "base/posix/global_descriptors.h"
 #include "chromecast/common/cast_paths.h"
 #include "chromecast/common/cast_resource_delegate.h"
+#include "chromecast/common/global_descriptors.h"
 #include "chromecast/shell/browser/cast_content_browser_client.h"
 #include "chromecast/shell/renderer/cast_content_renderer_client.h"
+#include "content/public/browser/browser_main_runner.h"
 #include "content/public/common/content_switches.h"
 #include "ui/base/resource/resource_bundle.h"
 
@@ -23,26 +27,74 @@
 }
 
 bool CastMainDelegate::BasicStartupComplete(int* exit_code) {
+  RegisterPathProvider();
+
   logging::LoggingSettings settings;
+#if defined(OS_ANDROID)
+  base::FilePath log_file;
+  PathService::Get(FILE_CAST_ANDROID_LOG, &log_file);
+  settings.logging_dest = logging::LOG_TO_ALL;
+  settings.log_file = log_file.value().c_str();
+  settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+#else
   settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+#endif  // defined(OS_ANDROID)
   logging::InitLogging(settings);
   // Time, process, and thread ID are available through logcat.
   logging::SetLogItems(true, true, false, false);
 
-  RegisterPathProvider();
-
   content::SetContentClient(&content_client_);
   return false;
 }
 
 void CastMainDelegate::PreSandboxStartup() {
+#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
+  // Create an instance of the CPU class to parse /proc/cpuinfo and cache the
+  // results. This data needs to be cached when file-reading is still allowed,
+  // since base::CPU expects to be callable later, when file-reading is no
+  // longer allowed.
+  base::CPU cpu_info;
+#endif
+
   InitializeResourceBundle();
 }
 
-void CastMainDelegate::ZygoteForked() {
+int CastMainDelegate::RunProcess(
+    const std::string& process_type,
+    const content::MainFunctionParams& main_function_params) {
+#if defined(OS_ANDROID)
+  if (!process_type.empty())
+    return -1;
+
+  // Note: Android must handle running its own browser process.
+  // See ChromeMainDelegateAndroid::RunProcess.
+  browser_runner_.reset(content::BrowserMainRunner::Create());
+  return browser_runner_->Initialize(main_function_params);
+#else
+  return -1;
+#endif  // defined(OS_ANDROID)
 }
 
+#if !defined(OS_ANDROID)
+void CastMainDelegate::ZygoteForked() {
+}
+#endif  // !defined(OS_ANDROID)
+
 void CastMainDelegate::InitializeResourceBundle() {
+#if defined(OS_ANDROID)
+  // On Android, the renderer runs with a different UID and can never access
+  // the file system. Use the file descriptor passed in at launch time.
+  int pak_fd =
+      base::GlobalDescriptors::GetInstance()->MaybeGet(kAndroidPakDescriptor);
+  if (pak_fd >= 0) {
+    ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
+        base::File(pak_fd), base::MemoryMappedFile::Region::kWholeFile);
+    ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile(
+        base::File(pak_fd), ui::SCALE_FACTOR_100P);
+    return;
+  }
+#endif
+
   resource_delegate_.reset(new CastResourceDelegate());
   // TODO(gunsch): Use LOAD_COMMON_RESOURCES once ResourceBundle no longer
   // hardcodes resource file names.
diff --git a/chromecast/shell/app/cast_main_delegate.h b/chromecast/shell/app/cast_main_delegate.h
index 8c9ed62..42409530 100644
--- a/chromecast/shell/app/cast_main_delegate.h
+++ b/chromecast/shell/app/cast_main_delegate.h
@@ -10,6 +10,10 @@
 #include "chromecast/shell/common/cast_content_client.h"
 #include "content/public/app/content_main_delegate.h"
 
+namespace content {
+class BrowserMainRunner;
+}  // namespace content
+
 namespace chromecast {
 
 class CastResourceDelegate;
@@ -27,7 +31,12 @@
   // content::ContentMainDelegate implementation:
   virtual bool BasicStartupComplete(int* exit_code) OVERRIDE;
   virtual void PreSandboxStartup() OVERRIDE;
+  virtual int RunProcess(
+      const std::string& process_type,
+      const content::MainFunctionParams& main_function_params) OVERRIDE;
+#if !defined(OS_ANDROID)
   virtual void ZygoteForked() OVERRIDE;
+#endif  // !defined(OS_ANDROID)
   virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE;
   virtual content::ContentRendererClient*
       CreateContentRendererClient() OVERRIDE;
@@ -40,6 +49,10 @@
   scoped_ptr<CastResourceDelegate> resource_delegate_;
   CastContentClient content_client_;
 
+#if defined(OS_ANDROID)
+  scoped_ptr<content::BrowserMainRunner> browser_runner_;
+#endif  // defined(OS_ANDROID)
+
   DISALLOW_COPY_AND_ASSIGN(CastMainDelegate);
 };
 
diff --git a/chromecast/shell/browser/android/cast_window_android.cc b/chromecast/shell/browser/android/cast_window_android.cc
new file mode 100644
index 0000000..65380b147
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_android.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/android/cast_window_android.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/renderer_preferences.h"
+#include "jni/CastWindowAndroid_jni.h"
+
+namespace chromecast {
+namespace shell {
+
+// static
+bool CastWindowAndroid::RegisterJni(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+CastWindowAndroid::CastWindowAndroid(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {
+}
+
+CastWindowAndroid::~CastWindowAndroid() {
+}
+
+// static
+CastWindowAndroid* CastWindowAndroid::CreateNewWindow(
+    content::BrowserContext* browser_context,
+    const GURL& url) {
+  content::WebContents::CreateParams create_params(browser_context);
+  create_params.routing_id = MSG_ROUTING_NONE;
+  content::WebContents* web_contents =
+      content::WebContents::Create(create_params);
+  CastWindowAndroid* shell = CreateCastWindowAndroid(
+      web_contents,
+      create_params.initial_size);
+  if (!url.is_empty())
+    shell->LoadURL(url);
+  return shell;
+}
+
+// static
+CastWindowAndroid* CastWindowAndroid::CreateCastWindowAndroid(
+    content::WebContents* web_contents,
+    const gfx::Size& initial_size) {
+  CastWindowAndroid* shell = new CastWindowAndroid(web_contents);
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jobject> shell_android(
+      CreateCastWindowView(shell));
+
+  shell->java_object_.Reset(env, shell_android.Release());
+  shell->web_contents_.reset(web_contents);
+  web_contents->SetDelegate(shell);
+
+  Java_CastWindowAndroid_initFromNativeWebContents(
+      env, shell->java_object_.obj(), reinterpret_cast<jint>(web_contents));
+
+  // Enabling hole-punching also requires runtime renderer preference
+  web_contents->GetMutableRendererPrefs()->
+      use_video_overlay_for_embedded_encrypted_video = true;
+  web_contents->GetRenderViewHost()->SyncRendererPrefs();
+
+  return shell;
+}
+
+void CastWindowAndroid::Close() {
+  // Note: if multiple windows becomes supported, this may close other devtools
+  // sessions.
+  content::DevToolsAgentHost::DetachAllClients();
+  CloseCastWindowView(java_object_.obj());
+  delete this;
+}
+
+void CastWindowAndroid::LoadURL(const GURL& url) {
+  content::NavigationController::LoadURLParams params(url);
+  params.transition_type = content::PageTransitionFromInt(
+      content::PAGE_TRANSITION_TYPED |
+      content::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  web_contents_->GetController().LoadURLWithParams(params);
+  web_contents_->Focus();
+}
+
+void CastWindowAndroid::AddNewContents(content::WebContents* source,
+                                       content::WebContents* new_contents,
+                                       WindowOpenDisposition disposition,
+                                       const gfx::Rect& initial_pos,
+                                       bool user_gesture,
+                                       bool* was_blocked) {
+  NOTIMPLEMENTED();
+  if (was_blocked) {
+    *was_blocked = true;
+  }
+}
+
+void CastWindowAndroid::CloseContents(content::WebContents* source) {
+  DCHECK_EQ(source, web_contents_.get());
+  Close();
+}
+
+bool CastWindowAndroid::CanOverscrollContent() const {
+  return false;
+}
+
+bool CastWindowAndroid::AddMessageToConsole(content::WebContents* source,
+                                            int32 level,
+                                            const base::string16& message,
+                                            int32 line_no,
+                                            const base::string16& source_id) {
+  return false;
+}
+
+void CastWindowAndroid::ActivateContents(content::WebContents* contents) {
+  DCHECK_EQ(contents, web_contents_.get());
+  contents->GetRenderViewHost()->Focus();
+}
+
+void CastWindowAndroid::DeactivateContents(content::WebContents* contents) {
+  DCHECK_EQ(contents, web_contents_.get());
+  contents->GetRenderViewHost()->Blur();
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/shell/browser/android/cast_window_android.h b/chromecast/shell/browser/android/cast_window_android.h
new file mode 100644
index 0000000..979aea9
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_android.h
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
+#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
+
+#include <jni.h>
+#include <vector>
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+class SiteInstance;
+class WebContents;
+}  // namespace content
+
+namespace chromecast {
+namespace shell {
+
+class CastWindowAndroid : public content::WebContentsDelegate,
+                          public content::WebContentsObserver {
+ public:
+  // Creates a new window and immediately loads the given URL.
+  static CastWindowAndroid* CreateNewWindow(
+      content::BrowserContext* browser_context,
+      const GURL& url);
+
+  virtual ~CastWindowAndroid();
+
+  void LoadURL(const GURL& url);
+  void Close();
+
+  // Registers the JNI methods for CastWindowAndroid.
+  static bool RegisterJni(JNIEnv* env);
+
+  // WebContentsDelegate implementation.
+  virtual void AddNewContents(content::WebContents* source,
+                              content::WebContents* new_contents,
+                              WindowOpenDisposition disposition,
+                              const gfx::Rect& initial_pos,
+                              bool user_gesture,
+                              bool* was_blocked) OVERRIDE;
+  virtual void CloseContents(content::WebContents* source) OVERRIDE;
+  virtual bool CanOverscrollContent() const OVERRIDE;
+  virtual bool AddMessageToConsole(content::WebContents* source,
+                                   int32 level,
+                                   const base::string16& message,
+                                   int32 line_no,
+                                   const base::string16& source_id) OVERRIDE;
+  virtual void ActivateContents(content::WebContents* contents) OVERRIDE;
+  virtual void DeactivateContents(content::WebContents* contents) OVERRIDE;
+
+ private:
+  explicit CastWindowAndroid(content::WebContents* web_contents);
+
+  // Helper to create a new CastWindowAndroid given a newly created WebContents.
+  static CastWindowAndroid* CreateCastWindowAndroid(
+      content::WebContents* web_contents,
+      const gfx::Size& initial_size);
+
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+  scoped_ptr<content::WebContents> web_contents_;
+
+  DISALLOW_COPY_AND_ASSIGN(CastWindowAndroid);
+};
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
diff --git a/chromecast/shell/browser/android/cast_window_manager.cc b/chromecast/shell/browser/android/cast_window_manager.cc
new file mode 100644
index 0000000..daa18af3
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_manager.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "chromecast/common/chromecast_config.h"
+#include "chromecast/common/pref_names.h"
+#include "chromecast/shell/browser/android/cast_window_android.h"
+#include "chromecast/shell/browser/cast_browser_context.h"
+#include "chromecast/shell/browser/cast_browser_main_parts.h"
+#include "chromecast/shell/browser/cast_content_browser_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "ipc/ipc_channel.h"
+#include "jni/CastWindowManager_jni.h"
+#include "url/gurl.h"
+
+namespace {
+
+base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject>>
+    g_window_manager = LAZY_INSTANCE_INITIALIZER;
+
+content::BrowserContext* g_browser_context = NULL;
+
+}  // namespace
+
+namespace chromecast {
+namespace shell {
+
+void SetBrowserContextAndroid(content::BrowserContext* browser_context) {
+  g_browser_context = browser_context;
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+CreateCastWindowView(CastWindowAndroid* shell) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  jobject j_window_manager = g_window_manager.Get().obj();
+  return Java_CastWindowManager_createCastWindow(env, j_window_manager);
+}
+
+void CloseCastWindowView(jobject shell_wrapper) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  jobject j_window_manager = g_window_manager.Get().obj();
+  Java_CastWindowManager_closeCastWindow(env, j_window_manager, shell_wrapper);
+}
+
+// Register native methods
+bool RegisterCastWindowManager(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+void Init(JNIEnv* env, jclass clazz, jobject obj) {
+  g_window_manager.Get().Reset(
+      base::android::ScopedJavaLocalRef<jobject>(env, obj));
+}
+
+jlong LaunchCastWindow(JNIEnv* env, jclass clazz, jstring jurl) {
+  DCHECK(g_browser_context);
+  GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
+  return reinterpret_cast<jlong>(
+      CastWindowAndroid::CreateNewWindow(g_browser_context, url));
+}
+
+void StopCastWindow(JNIEnv* env, jclass clazz, jlong nativeCastWindow) {
+  CastWindowAndroid* window =
+      reinterpret_cast<CastWindowAndroid*>(nativeCastWindow);
+  DCHECK(window);
+  window->Close();
+}
+
+void EnableDevTools(JNIEnv* env, jclass clazz, jboolean enable) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  // The specific port value doesn't matter since Android uses Unix domain
+  // sockets, only whether or not it is zero.
+  chromecast::ChromecastConfig::GetInstance()->pref_service()->
+      SetInteger(prefs::kRemoteDebuggingPort, enable ? 1 : 0);
+}
+
+}  // namespace shell
+}  // namespace chromecast
diff --git a/chromecast/shell/browser/android/cast_window_manager.h b/chromecast/shell/browser/android/cast_window_manager.h
new file mode 100644
index 0000000..9a811a91
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_manager.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
+#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+
+class CastWindowAndroid;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace chromecast {
+namespace shell {
+
+// Sets the browser context to use for creating windows. Must be invoked before
+// a LaunchCastWindow call.
+void SetBrowserContextAndroid(content::BrowserContext* browser_context);
+
+// Given a CastWindowAndroid instance, creates and returns a Java wrapper.
+base::android::ScopedJavaLocalRef<jobject>
+CreateCastWindowView(CastWindowAndroid* shell);
+
+// Closes a previously created Java wrapper.
+void CloseCastWindowView(jobject shell_wrapper);
+
+// Registers the CastWindowManager native methods.
+bool RegisterCastWindowManager(JNIEnv* env);
+
+}  // namespace shell
+}  // namespace chromecast
+
+#endif  // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
diff --git a/chromecast/shell/browser/cast_browser_main_parts.cc b/chromecast/shell/browser/cast_browser_main_parts.cc
index 3f7b358..24e1342 100644
--- a/chromecast/shell/browser/cast_browser_main_parts.cc
+++ b/chromecast/shell/browser/cast_browser_main_parts.cc
@@ -5,6 +5,7 @@
 #include "chromecast/shell/browser/cast_browser_main_parts.h"
 
 #include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
 #include "base/prefs/pref_registry_simple.h"
 #include "chromecast/common/chromecast_config.h"
 #include "chromecast/metrics/cast_metrics_service_client.h"
@@ -18,6 +19,10 @@
 #include "chromecast/shell/browser/webui/webui_cast.h"
 #include "content/public/common/content_switches.h"
 
+#if defined(OS_ANDROID)
+#include "net/android/network_change_notifier_factory_android.h"
+#endif  // defined(OS_ANDROID)
+
 namespace chromecast {
 namespace shell {
 
@@ -60,12 +65,19 @@
 }
 
 void CastBrowserMainParts::PreMainMessageLoopStart() {
+#if defined(OS_ANDROID)
+  net::NetworkChangeNotifier::SetFactory(
+      new net::NetworkChangeNotifierFactoryAndroid());
+#else
   net::NetworkChangeNotifier::SetFactory(
       new NetworkChangeNotifierFactoryCast());
+#endif  // defined(OS_ANDROID)
 }
 
 void CastBrowserMainParts::PostMainMessageLoopStart() {
-  NOTIMPLEMENTED();
+#if defined(OS_ANDROID)
+  base::MessageLoopForUI::current()->Start();
+#endif  // defined(OS_ANDROID)
 }
 
 int CastBrowserMainParts::PreCreateThreads() {
diff --git a/chromecast/shell/browser/cast_content_browser_client.cc b/chromecast/shell/browser/cast_content_browser_client.cc
index f8d897e..919b190 100644
--- a/chromecast/shell/browser/cast_content_browser_client.cc
+++ b/chromecast/shell/browser/cast_content_browser_client.cc
@@ -6,12 +6,16 @@
 
 #include "base/command_line.h"
 #include "base/i18n/rtl.h"
+#include "base/path_service.h"
+#include "chromecast/common/cast_paths.h"
+#include "chromecast/common/global_descriptors.h"
 #include "chromecast/shell/browser/cast_browser_context.h"
 #include "chromecast/shell/browser/cast_browser_main_parts.h"
 #include "chromecast/shell/browser/cast_browser_process.h"
 #include "chromecast/shell/browser/geolocation/cast_access_token_store.h"
 #include "chromecast/shell/browser/url_request_context_factory.h"
 #include "content/public/browser/certificate_request_result_type.h"
+#include "content/public/browser/file_descriptor_info.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_descriptors.h"
 #include "content/public/common/content_switches.h"
@@ -144,6 +148,19 @@
     const base::CommandLine& command_line,
     int child_process_id,
     std::vector<content::FileDescriptorInfo>* mappings) {
+#if defined(OS_ANDROID)
+  int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+  base::FilePath pak_file;
+  CHECK(PathService::Get(FILE_CAST_PAK, &pak_file));
+  base::File pak_with_flags(pak_file, flags);
+  if (!pak_with_flags.IsValid()) {
+    NOTREACHED() << "Failed to open file when creating renderer process: "
+                 << "cast_shell.pak";
+  }
+  mappings->push_back(content::FileDescriptorInfo(
+      kAndroidPakDescriptor,
+      base::FileDescriptor(base::File(pak_file, flags))));
+#endif  // defined(OS_ANDROID)
 }
 
 }  // namespace shell
diff --git a/chromecast/shell/browser/cast_http_user_agent_settings.cc b/chromecast/shell/browser/cast_http_user_agent_settings.cc
index fbc43b3..e4a1537d 100644
--- a/chromecast/shell/browser/cast_http_user_agent_settings.cc
+++ b/chromecast/shell/browser/cast_http_user_agent_settings.cc
@@ -11,6 +11,10 @@
 #include "net/http/http_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if defined(OS_ANDROID)
+#include "ui/base/l10n/l10n_util_android.h"
+#endif  // defined(OS_ANDROID)
+
 namespace chromecast {
 namespace shell {
 
@@ -26,7 +30,12 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   if (accept_language_.empty()) {
     accept_language_ = net::HttpUtil::GenerateAcceptLanguageHeader(
-        l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES));
+#if defined(OS_ANDROID)
+        l10n_util::GetDefaultLocale()
+#else
+        l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES)
+#endif  // defined(OS_ANDROID)
+        );
   }
   return accept_language_;
 }
diff --git a/chromecast/shell/browser/url_request_context_factory.cc b/chromecast/shell/browser/url_request_context_factory.cc
index d214db3..0bac1b3f 100644
--- a/chromecast/shell/browser/url_request_context_factory.cc
+++ b/chromecast/shell/browser/url_request_context_factory.cc
@@ -63,8 +63,10 @@
         request_context_.reset(factory_->CreateMediaRequestContext());
       } else {
         request_context_.reset(factory_->CreateSystemRequestContext());
+#if defined(USE_NSS)
         // Set request context used by NSS for Crl requests.
         net::SetURLRequestContextForNSSHttpIO(request_context_.get());
+#endif  // defined(USE_NSS)
       }
     }
     return request_context_.get();
@@ -144,6 +146,14 @@
   // because it registers itself to pref notification observer which is not
   // thread safe.
   http_user_agent_settings_.reset(new CastHttpUserAgentSettings());
+
+  // Proxy config service should be initialized in UI thread, since
+  // ProxyConfigServiceDelegate on Android expects UI thread.
+  proxy_config_service_.reset(net::ProxyService::CreateSystemProxyConfigService(
+      content::BrowserThread::GetMessageLoopProxyForThread(
+          content::BrowserThread::IO),
+      content::BrowserThread::GetMessageLoopProxyForThread(
+          content::BrowserThread::FILE)));
 }
 
 net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter(
@@ -202,13 +212,7 @@
   http_server_properties_.reset(new net::HttpServerPropertiesImpl);
 
   proxy_service_.reset(net::ProxyService::CreateUsingSystemProxyResolver(
-      net::ProxyService::CreateSystemProxyConfigService(
-          content::BrowserThread::GetMessageLoopProxyForThread(
-              content::BrowserThread::IO),
-          content::BrowserThread::GetMessageLoopProxyForThread(
-              content::BrowserThread::FILE)),
-      0,
-      NULL));
+      proxy_config_service_.release(), 0, NULL));
   system_dependencies_initialized_ = true;
 }
 
diff --git a/chromecast/shell/browser/url_request_context_factory.h b/chromecast/shell/browser/url_request_context_factory.h
index 03d062d..304cdea 100644
--- a/chromecast/shell/browser/url_request_context_factory.h
+++ b/chromecast/shell/browser/url_request_context_factory.h
@@ -11,6 +11,7 @@
 namespace net {
 class HttpTransactionFactory;
 class HttpUserAgentSettings;
+class ProxyConfigService;
 class URLRequestJobFactory;
 }  // namespace net
 
@@ -83,6 +84,7 @@
   scoped_ptr<net::CertVerifier> cert_verifier_;
   scoped_refptr<net::SSLConfigService> ssl_config_service_;
   scoped_ptr<net::TransportSecurityState> transport_security_state_;
+  scoped_ptr<net::ProxyConfigService> proxy_config_service_;
   scoped_ptr<net::ProxyService> proxy_service_;
   scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory_;
   scoped_ptr<net::HttpServerProperties> http_server_properties_;