Initial commit for VideoView2Impl

Test: build
Change-Id: I2f5e7f85b5a7358c707cf3897f354ead42980956
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 6c408c3..bbd2afa 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -32,6 +32,8 @@
 
 LOCAL_MULTILIB := first
 
+LOCAL_JAVA_LIBRARIES += android-support-annotations
+
 # Embed native libraries in package, rather than installing to /system/lib*.
 # TODO: Find a right way to include libs in the apk. b/72066556
 LOCAL_MODULE_TAGS := samples
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_arrow_back.xml b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..5aba8c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..ac22a4b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_cast.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_left.xml b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..8336d17
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_right.xml b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..fb2ce09
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_forward_30.xml b/packages/MediaComponents/res/drawable/ic_forward_30.xml
new file mode 100644
index 0000000..7efdf16
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_forward_30.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <group>
+        <clip-path android:pathData="M24,24H0V0h24v24z M 0,0" />
+    <path
+        android:pathData="M9.6 13.5h.4c.2 0 .4,-.1.5,-.2s.2,-.2.2,-.4v-.2s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.5s-.1.1,-.2.1,-.1.1,-.1.2v.2h-1c0,-.2 0,-.3.1,-.5s.2,-.3.3,-.4.3,-.2.4,-.2.4,-.1.5,-.1c.2 0 .4 0 .6.1s.3.1.5.2.2.2.3.4.1.3.1.5v.3s-.1.2,-.1.3,-.1.2,-.2.2,-.2.1,-.3.2c.2.1.4.2.5.4s.2.4.2.6c0 .2 0 .4,-.1.5s-.2.3,-.3.4,-.3.2,-.5.2,-.4.1,-.6.1c-.2 0,-.4 0,-.5,-.1s-.3,-.1,-.5,-.2,-.2,-.2,-.3,-.4,-.1,-.4,-.1,-.6h.8v.2s.1.1.1.2.1.1.2.1h.5s.1,-.1.2,-.1.1,-.1.1,-.2v-.5s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.6v-.7zm5.7.7c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1.3.2.5.3.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5zM4 13c0 4.4 3.6 8 8 8s8,-3.6 8,-8h-2c0 3.3,-2.7 6,-6 6s-6,-2.7,-6,-6 2.7,-6 6,-6v4l5,-5,-5,-5v4c-4.4 0,-8 3.6,-8 8z"
+        android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen.xml b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
new file mode 100644
index 0000000..4b4f6bc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
new file mode 100644
index 0000000..bc204e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
new file mode 100644
index 0000000..73be228
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
new file mode 100644
index 0000000..9d39def
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_rewind_10.xml b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
new file mode 100644
index 0000000..ae586b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <group>
+        <clip-path android:pathData="M0,0h24v24H0V0z M 0,0" />
+    <path
+        android:pathData="M12 5V1L7 6l5 5V7c3.3 0 6 2.7 6 6s-2.7 6,-6 6,-6,-2.7,-6,-6H4c0 4.4 3.6 8 8 8s8,-3.6 8,-8,-3.6,-8,-8,-8zm-1.1 11H10v-3.3L9 13v-.7l1.8,-.6h.1V16zm4.3,-1.8c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1c.2.1.3.2.5.3s.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5z"
+        android:fillColor="#FFFFFF"/>
+    </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_next.xml b/packages/MediaComponents/res/drawable/ic_skip_next.xml
new file mode 100644
index 0000000..b1f2812
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_next.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_previous.xml b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
new file mode 100644
index 0000000..81da314
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
new file mode 100644
index 0000000..ff4f12a
--- /dev/null
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#55000000"
+    android:orientation="vertical"
+    android:layoutDirection="ltr">
+
+    <RelativeLayout
+        android:id="@+id/title_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <RadioButton
+            android:id="@+id/back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:checked="true"
+            android:visibility="gone"/>
+
+        <TextView
+            android:id="@+id/title_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@id/back"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="15dp"
+            android:paddingTop="4dp"
+            android:paddingStart="4dp"
+            android:paddingEnd="4dp"
+            android:textSize="20sp"
+            android:text="North by Northwest"
+            android:textColor="#FFFFFFFF" />
+
+        <ImageButton
+            android:id="@+id/cast"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            style="@style/TitleBarButton.MediaRouteButton"/>
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:paddingTop="4dp"
+        android:orientation="horizontal">
+
+        <ImageButton android:id="@+id/prev" style="@style/TransportControlsButton.Previous" />
+        <ImageButton android:id="@+id/rew" style="@style/TransportControlsButton.Rew" />
+        <ImageButton android:id="@+id/pause" style="@style/TransportControlsButton.Pause" />
+        <ImageButton android:id="@+id/ffwd" style="@style/TransportControlsButton.Ffwd" />
+        <ImageButton android:id="@+id/next" style="@style/TransportControlsButton.Next" />
+
+    </LinearLayout>
+
+    <SeekBar
+        android:id="@+id/mediacontroller_progress"
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:padding="0dp"
+        android:progressTint="#FFFFFFFF"
+        android:thumbTint="#FFFFFFFF"/>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="15dp"
+        android:paddingRight="15dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/time_current"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"
+            android:paddingEnd="4dp"
+            android:paddingStart="4dp"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:textColor="#FFFFFF" />
+
+        <TextView
+            android:id="@+id/time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_toRightOf="@id/time_current"
+            android:paddingStart="4dp"
+            android:paddingEnd="4dp"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:textColor="#BBBBBB" />
+
+        <ImageButton
+            android:id="@+id/overflow"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.Overflow"/>
+
+        <ImageButton
+            android:id="@+id/fullscreen"
+            android:layout_toLeftOf="@id/overflow"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.FullScreen"/>
+
+        <ImageButton
+            android:id="@+id/cc"
+            android:scaleType="fitCenter"
+            android:layout_toLeftOf="@id/fullscreen"
+            android:layout_centerVertical="true"
+            style="@style/BottomBarButton.CC" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values/attrs.xml b/packages/MediaComponents/res/values/attrs.xml
index e37285b..6175b11 100644
--- a/packages/MediaComponents/res/values/attrs.xml
+++ b/packages/MediaComponents/res/values/attrs.xml
@@ -41,4 +41,5 @@
     <attr name="mediaRouteControlPanelThemeOverlay" format="reference" />
 
     <attr name="mediaRouteTheme" format="reference" />
+    <attr name="enableControlView" format="boolean" />
 </resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index ca9dd6b..e93269c 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -81,4 +81,14 @@
 
     <!-- Placeholder text indicating that the user is currently casting screen. [CHAR LIMIT=50] -->
     <string name="mr_controller_casting_screen">Casting screen</string>
+
+    <string name="lockscreen_pause_button_content_description">Pause</string>
+    <string name="lockscreen_play_button_content_description">Play</string>
+
+    <!-- Text for error alert when a video container is not valid for progressive download/playback. -->
+    <string name="VideoView2_error_text_invalid_progressive_playback">This video isn\'t valid for streaming to this device.</string>
+    <!-- Text for error alert when a video cannot be played. It can be used by any app. -->
+    <string name="VideoView2_error_text_unknown">Can\'t play this video.</string>
+    <!-- Button to close error alert when a video cannot be played. -->
+    <string name="VideoView2_error_button">OK</string>
 </resources>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
new file mode 100644
index 0000000..d31b41d
--- /dev/null
+++ b/packages/MediaComponents/res/values/style.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="TransportControlsButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">70dp</item>
+        <item name="android:layout_height">40dp</item>
+    </style>
+
+    <style name="TransportControlsButton.Previous">
+        <item name="android:src">@drawable/ic_skip_previous</item>
+    </style>
+
+    <style name="TransportControlsButton.Next">
+        <item name="android:src">@drawable/ic_skip_next</item>
+    </style>
+
+    <style name="TransportControlsButton.Pause">
+        <item name="android:src">@drawable/ic_pause_circle_filled</item>
+    </style>
+
+    <style name="TransportControlsButton.Ffwd">
+        <item name="android:src">@drawable/ic_forward_30</item>
+    </style>
+
+    <style name="TransportControlsButton.Rew">
+        <item name="android:src">@drawable/ic_rewind_10</item>
+    </style>
+
+
+    <style name="TitleBarButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">36dp</item>
+        <item name="android:layout_height">36dp</item>
+        <item name="android:layout_margin">10dp</item>
+    </style>
+
+    <style name="TitleBarButton.MediaRouteButton">
+        <item name="android:src">@drawable/ic_cast</item>
+    </style>
+
+
+    <style name="BottomBarButton">
+        <item name="android:background">@null</item>
+        <item name="android:layout_width">24dp</item>
+        <item name="android:layout_height">24dp</item>
+        <item name="android:layout_margin">10dp</item>
+    </style>
+
+    <style name="BottomBarButton.CC">
+        <item name="android:src">@drawable/ic_media_cc_disabled</item>
+    </style>
+
+    <style name="BottomBarButton.FullScreen">
+        <item name="android:src">@drawable/ic_fullscreen</item>
+    </style>
+
+    <style name="BottomBarButton.Overflow">
+        <item name="android:src">@drawable/ic_chevron_right</item>
+    </style>
+
+</resources>
+
diff --git a/packages/MediaComponents/res/values/symbols.xml b/packages/MediaComponents/res/values/symbols.xml
new file mode 100644
index 0000000..ee0e8c6
--- /dev/null
+++ b/packages/MediaComponents/res/values/symbols.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+    <!--java-symbol type="id" name="cc" />
+    <java-symbol type="id" name="ffwd" />
+    <java-symbol type="id" name="mediacontroller_progress" />
+    <java-symbol type="id" name="next" />
+    <java-symbol type="id" name="pause" />
+    <java-symbol type="id" name="prev" />
+    <java-symbol type="id" name="rew" />
+    <java-symbol type="id" name="time" />
+    <java-symbol type="id" name="time_current" /-->
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index f015b86..633a342 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,14 +18,16 @@
 
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
-import android.media.update.MediaController2Provider;
+import android.media.update.MediaControlView2Provider;
 import android.media.update.VideoView2Provider;
 import android.media.update.StaticProvider;
 import android.media.update.ViewProvider;
-import android.widget.MediaController2;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
-import com.android.widget.MediaController2Impl;
+import com.android.widget.MediaControlView2Impl;
 import com.android.widget.VideoView2Impl;
 
 public class ApiFactory implements StaticProvider {
@@ -36,13 +38,15 @@
     }
 
     @Override
-    public MediaController2Provider createMediaController2(
-            MediaController2 instance, ViewProvider superProvider) {
-        return new MediaController2Impl(instance, superProvider);
+    public MediaControlView2Provider createMediaControlView2(
+            MediaControlView2 instance, ViewProvider superProvider) {
+        return new MediaControlView2Impl(instance, superProvider);
     }
 
     @Override
-    public VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider) {
-        return new VideoView2Impl(instance, superProvider);
+    public VideoView2Provider createVideoView2(
+            VideoView2 instance, ViewProvider superProvider,
+            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        return new VideoView2Impl(instance, superProvider, attrs, defStyleAttr, defStyleRes);
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 07edb03..26f858c 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -39,11 +39,11 @@
         mLibTheme = libTheme;
     }
 
-    public Resources getLibResources() {
-        return mLibResources;
+    public static Resources getLibResources() {
+        return sInstance.mLibResources;
     }
 
-    public Resources.Theme getLibTheme() {
-        return mLibTheme;
+    public static Resources.Theme getLibTheme() {
+        return sInstance.mLibTheme;
     }
 }
diff --git a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
similarity index 72%
rename from packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
rename to packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index d322a20..8053245e 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -16,21 +16,22 @@
 
 package com.android.widget;
 
-import android.graphics.Canvas;
 import android.media.session.MediaController;
-import android.media.update.MediaController2Provider;
+import android.media.update.MediaControlView2Provider;
 import android.media.update.ViewProvider;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.MediaController2;
+import android.widget.MediaControlView2;
 
-public class MediaController2Impl implements MediaController2Provider {
-    private final MediaController2 mInstance;
+public class MediaControlView2Impl implements MediaControlView2Provider {
+    private final MediaControlView2 mInstance;
     private final ViewProvider mSuperProvider;
 
-    public MediaController2Impl(MediaController2 instance, ViewProvider superProvider) {
+    static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
+    static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+
+    public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
         mInstance = instance;
         mSuperProvider = superProvider;
 
@@ -43,11 +44,6 @@
     }
 
     @Override
-    public void setAnchorView_impl(View view) {
-        // TODO: Implement
-    }
-
-    @Override
     public void show_impl() {
         // TODO: Implement
     }
@@ -69,11 +65,6 @@
     }
 
     @Override
-    public void setPrevNextListeners_impl(OnClickListener next, OnClickListener prev) {
-        // TODO: Implement
-    }
-
-    @Override
     public void showCCButton_impl() {
         // TODO: Implement
     }
@@ -125,33 +116,9 @@
     }
 
     @Override
-    public void onAttachedToWindow_impl() {
-        mSuperProvider.onAttachedToWindow_impl();
-        // TODO: Implement
-    }
-
-    @Override
-    public void onDetachedFromWindow_impl() {
-        mSuperProvider.onDetachedFromWindow_impl();
-        // TODO: Implement
-    }
-
-    @Override
-    public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
-        mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
-        // TODO: Implement
-    }
-
-    @Override
-    public void draw_impl(Canvas canvas) {
-        mSuperProvider.draw_impl(canvas);
-        // TODO: Implement
-    }
-
-    @Override
     public CharSequence getAccessibilityClassName_impl() {
         // TODO: Implement
-        return MediaController2.class.getName();
+        return MediaControlView2.class.getName();
     }
 
     @Override
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
new file mode 100644
index 0000000..9071967
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleTrack.RenderingWidget;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+class SubtitleView extends FrameLayout implements Anchor {
+    private static final String TAG = "SubtitleView";
+
+    private RenderingWidget mSubtitleWidget;
+    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
+
+    public SubtitleView(Context context) {
+        this(context, null);
+    }
+
+    public SubtitleView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SubtitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SubtitleView(
+            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+        if (mSubtitleWidget == subtitleWidget) {
+            return;
+        }
+
+        final boolean attachedToWindow = isAttachedToWindow();
+        if (mSubtitleWidget != null) {
+            if (attachedToWindow) {
+                mSubtitleWidget.onDetachedFromWindow();
+            }
+
+            mSubtitleWidget.setOnChangedListener(null);
+        }
+        mSubtitleWidget = subtitleWidget;
+
+        if (subtitleWidget != null) {
+            if (mSubtitlesChangedListener == null) {
+                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+                    @Override
+                    public void onChanged(RenderingWidget renderingWidget) {
+                        invalidate();
+                    }
+                };
+            }
+
+            setWillNotDraw(false);
+            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+            if (attachedToWindow) {
+                subtitleWidget.onAttachedToWindow();
+                requestLayout();
+            }
+        } else {
+            setWillNotDraw(true);
+        }
+
+        invalidate();
+    }
+
+    @Override
+    public Looper getSubtitleLooper() {
+        return Looper.getMainLooper();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onAttachedToWindow();
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onDetachedFromWindow();
+        }
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mSubtitleWidget != null) {
+            final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+            final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+            mSubtitleWidget.setSize(width, height);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (mSubtitleWidget != null) {
+            final int saveCount = canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingTop());
+            mSubtitleWidget.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return SubtitleView.class.getName();
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
new file mode 100644
index 0000000..800be8e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_SURFACEVIEW;
+
+class VideoSurfaceView extends SurfaceView implements VideoViewInterface, SurfaceHolder.Callback {
+    private static final String TAG = "VideoSurfaceView";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+    private SurfaceHolder mSurfaceHolder = null;
+    private SurfaceListener mSurfaceListener = null;
+    private MediaPlayer mMediaPlayer;
+    // A flag to indicate taking over other view should be proceed.
+    private boolean mIsTakingOverOldView;
+    private VideoViewInterface mOldView;
+
+
+    public VideoSurfaceView(Context context) {
+        this(context, null);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr,
+                            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        getHolder().addCallback(this);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements VideoViewInterface
+    ////////////////////////////////////////////////////
+
+    @Override
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+        Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
+        if (mp == null || !hasAvailableSurface()) {
+            return false;
+        }
+        mp.setDisplay(mSurfaceHolder);
+        return true;
+    }
+
+    @Override
+    public void setSurfaceListener(SurfaceListener l) {
+        mSurfaceListener = l;
+    }
+
+    @Override
+    public int getViewType() {
+        return VIEW_TYPE_SURFACEVIEW;
+    }
+
+    @Override
+    public void setMediaPlayer(MediaPlayer mp) {
+        mMediaPlayer = mp;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        }
+    }
+
+    @Override
+    public void takeOver(@NonNull VideoViewInterface oldView) {
+        if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+            ((View) oldView).setVisibility(GONE);
+            mIsTakingOverOldView = false;
+            mOldView = null;
+            if (mSurfaceListener != null) {
+                mSurfaceListener.onSurfaceTakeOverDone(this);
+            }
+        } else {
+            mIsTakingOverOldView = true;
+            mOldView = oldView;
+        }
+    }
+
+    @Override
+    public boolean hasAvailableSurface() {
+        return (mSurfaceHolder != null && mSurfaceHolder.getSurface() != null);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements SurfaceHolder.Callback
+    ////////////////////////////////////////////////////
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.d(TAG, "surfaceCreated: mSurfaceHolder: " + mSurfaceHolder + ", new holder: " + holder);
+        mSurfaceHolder = holder;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        } else {
+            assignSurfaceToMediaPlayer(mMediaPlayer);
+        }
+
+        if (mSurfaceListener != null) {
+            Rect rect = mSurfaceHolder.getSurfaceFrame();
+            mSurfaceListener.onSurfaceCreated(this, rect.width(), rect.height());
+        }
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceChanged(this, width, height);
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // After we return from this we can't use the surface any more
+        mSurfaceHolder = null;
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceDestroyed(this);
+        }
+    }
+
+    // TODO: Investigate the way to move onMeasure() code into FrameLayout.
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+        int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+        if (DEBUG) {
+            Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + MeasureSpec.toString(heightMeasureSpec) + ")");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+            Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+            Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+        }
+
+        int width = getDefaultSize(videoWidth, widthMeasureSpec);
+        int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+        if (videoWidth > 0 && videoHeight > 0) {
+            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+                // the size is fixed
+                width = widthSpecSize;
+                height = heightSpecSize;
+
+                // for compatibility, we adjust size based on aspect ratio
+                if (videoWidth * height < width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too wide, correcting");
+                    }
+                    width = height * videoWidth / videoHeight;
+                } else if (videoWidth * height > width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too tall, correcting");
+                    }
+                    height = width * videoHeight / videoWidth;
+                }
+            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+                // only the width is fixed, adjust the height to match aspect ratio if possible
+                width = widthSpecSize;
+                height = width * videoHeight / videoWidth;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    height = heightSpecSize;
+                }
+            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+                // only the height is fixed, adjust the width to match aspect ratio if possible
+                height = heightSpecSize;
+                width = height * videoWidth / videoHeight;
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    width = widthSpecSize;
+                }
+            } else {
+                // neither the width nor the height are fixed, try to use actual video size
+                width = videoWidth;
+                height = videoHeight;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // too tall, decrease both width and height
+                    height = heightSpecSize;
+                    width = height * videoWidth / videoHeight;
+                }
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // too wide, decrease both width and height
+                    width = widthSpecSize;
+                    height = width * videoHeight / videoWidth;
+                }
+            }
+        } else {
+            // no size yet, just adopt the given spec sizes
+        }
+        setMeasuredDimension(width, height);
+        if (DEBUG) {
+            Log.i(TAG, "end of onMeasure()");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ViewType: SurfaceView / Visibility: " + getVisibility()
+                + " / surfaceHolder: " + mSurfaceHolder;
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
new file mode 100644
index 0000000..f240301
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.widget;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
+
+@RequiresApi(26)
+class VideoTextureView extends TextureView
+        implements VideoViewInterface, TextureView.SurfaceTextureListener {
+    private static final String TAG = "VideoTextureView";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+
+    private SurfaceTexture mSurfaceTexture;
+    private Surface mSurface;
+    private SurfaceListener mSurfaceListener;
+    private MediaPlayer mMediaPlayer;
+    // A flag to indicate taking over other view should be proceed.
+    private boolean mIsTakingOverOldView;
+    private VideoViewInterface mOldView;
+
+    public VideoTextureView(Context context) {
+        this(context, null);
+    }
+
+    public VideoTextureView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public VideoTextureView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setSurfaceTextureListener(this);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements VideoViewInterface
+    ////////////////////////////////////////////////////
+
+    @Override
+    public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+        Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
+        if (mp == null || !hasAvailableSurface()) {
+            // Surface is not ready.
+            return false;
+        }
+        mp.setSurface(mSurface);
+        return true;
+    }
+
+    @Override
+    public void setSurfaceListener(SurfaceListener l) {
+        mSurfaceListener = l;
+    }
+
+    @Override
+    public int getViewType() {
+        return VIEW_TYPE_TEXTUREVIEW;
+    }
+
+    @Override
+    public void setMediaPlayer(MediaPlayer mp) {
+        mMediaPlayer = mp;
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        }
+    }
+
+    @Override
+    public void takeOver(@NonNull VideoViewInterface oldView) {
+        if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+            ((View) oldView).setVisibility(GONE);
+            mIsTakingOverOldView = false;
+            mOldView = null;
+            if (mSurfaceListener != null) {
+                mSurfaceListener.onSurfaceTakeOverDone(this);
+            }
+        } else {
+            mIsTakingOverOldView = true;
+            mOldView = oldView;
+        }
+    }
+
+    @Override
+    public boolean hasAvailableSurface() {
+        return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
+    }
+
+    ////////////////////////////////////////////////////
+    // implements TextureView.SurfaceTextureListener
+    ////////////////////////////////////////////////////
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+        Log.d(TAG, "onSurfaceTextureAvailable: mSurfaceTexture: " + mSurfaceTexture
+                + ", new surface: " + surfaceTexture);
+        mSurfaceTexture = surfaceTexture;
+        mSurface = new Surface(mSurfaceTexture);
+        if (mIsTakingOverOldView) {
+            takeOver(mOldView);
+        } else {
+            assignSurfaceToMediaPlayer(mMediaPlayer);
+        }
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceCreated(this, width, height);
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceChanged(this, width, height);
+        }
+        // requestLayout();  // TODO: figure out if it should be called here?
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // no-op
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+        if (mSurfaceListener != null) {
+            mSurfaceListener.onSurfaceDestroyed(this);
+        }
+        mSurfaceTexture = null;
+        mSurface = null;
+        return true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+        int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+        if (DEBUG) {
+            Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+                    + MeasureSpec.toString(heightMeasureSpec) + ")");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+            Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+            Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+        }
+
+        int width = getDefaultSize(videoWidth, widthMeasureSpec);
+        int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+        if (videoWidth > 0 && videoHeight > 0) {
+            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+                // the size is fixed
+                width = widthSpecSize;
+                height = heightSpecSize;
+
+                // for compatibility, we adjust size based on aspect ratio
+                if (videoWidth * height  < width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too wide, correcting");
+                    }
+                    width = height * videoWidth / videoHeight;
+                } else if (videoWidth * height  > width * videoHeight) {
+                    if (DEBUG) {
+                        Log.d(TAG, "image too tall, correcting");
+                    }
+                    height = width * videoHeight / videoWidth;
+                }
+            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+                // only the width is fixed, adjust the height to match aspect ratio if possible
+                width = widthSpecSize;
+                height = width * videoHeight / videoWidth;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    height = heightSpecSize;
+                }
+            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+                // only the height is fixed, adjust the width to match aspect ratio if possible
+                height = heightSpecSize;
+                width = height * videoWidth / videoHeight;
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    width = widthSpecSize;
+                }
+            } else {
+                // neither the width nor the height are fixed, try to use actual video size
+                width = videoWidth;
+                height = videoHeight;
+                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // too tall, decrease both width and height
+                    height = heightSpecSize;
+                    width = height * videoWidth / videoHeight;
+                }
+                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // too wide, decrease both width and height
+                    width = widthSpecSize;
+                    height = width * videoHeight / videoWidth;
+                }
+            }
+        } else {
+            // no size yet, just adopt the given spec sizes
+        }
+        setMeasuredDimension(width, height);
+        if (DEBUG) {
+            Log.i(TAG, "end of onMeasure()");
+            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ViewType: TextureView / Visibility: " + getVisibility()
+                + " / surfaceTexture: " + mSurfaceTexture;
+
+    }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 66b5ed5..19a41de 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,220 +16,486 @@
 
 package com.android.widget;
 
-import android.graphics.Canvas;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
 import android.media.MediaPlayer;
+import android.media.Cea708CaptionRenderer;
+import android.media.ClosedCaptionRenderer;
+import android.media.Metadata;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.media.TtmlRenderer;
+import android.media.WebVttRenderer;
 import android.media.update.VideoView2Provider;
 import android.media.update.ViewProvider;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.MediaController2;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.MediaControlView2;
 import android.widget.VideoView2;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
 public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
+    private static final String TAG = "VideoView2";
+    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
 
     private final VideoView2 mInstance;
     private final ViewProvider mSuperProvider;
 
-    public VideoView2Impl(VideoView2 instance, ViewProvider superProvider) {
+    private static final int STATE_ERROR = -1;
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_PREPARING = 1;
+    private static final int STATE_PREPARED = 2;
+    private static final int STATE_PLAYING = 3;
+    private static final int STATE_PAUSED = 4;
+    private static final int STATE_PLAYBACK_COMPLETED = 5;
+
+    private final AudioManager mAudioManager;
+    private AudioAttributes mAudioAttributes;
+    private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
+    private int mAudioSession;
+
+    private VideoView2.OnPreparedListener mOnPreparedListener;
+    private VideoView2.OnCompletionListener mOnCompletionListener;
+    private VideoView2.OnErrorListener mOnErrorListener;
+    private VideoView2.OnInfoListener mOnInfoListener;
+    private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+
+    private VideoViewInterface mCurrentView;
+    private VideoTextureView mTextureView;
+    private VideoSurfaceView mSurfaceView;
+
+    private MediaPlayer mMediaPlayer;
+    private MediaControlView2 mMediaControlView;
+    private MediaSession mMediaSession;
+
+    private PlaybackState.Builder mStateBuilder;
+    private int mTargetState = STATE_IDLE;
+    private int mCurrentState = STATE_IDLE;
+    private int mCurrentBufferPercentage;
+    private int mSeekWhenPrepared;  // recording the seek position while preparing
+
+    private int mSurfaceWidth;
+    private int mSurfaceHeight;
+    private int mVideoWidth;
+    private int mVideoHeight;
+
+    private boolean mCCEnabled;
+    private int mSelectedTrackIndex;
+
+    private SubtitleView mSubtitleView;
+    private float mSpeed;
+    // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
+    // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
+    private float mFallbackSpeed;  // keep the original speed before 'pause' is called.
+
+    public VideoView2Impl(
+            VideoView2 instance, ViewProvider superProvider,
+            @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         mInstance = instance;
         mSuperProvider = superProvider;
 
-        // TODO: Implement
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+        mSurfaceWidth = 0;
+        mSurfaceHeight = 0;
+        mSpeed = 1.0f;
+        mFallbackSpeed = mSpeed;
+
+        mAudioManager = (AudioManager) mInstance.getContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+        mInstance.setFocusable(true);
+        mInstance.setFocusableInTouchMode(true);
+        mInstance.requestFocus();
+
+        // TODO: try to keep a single child at a time rather than always having both.
+        mTextureView = new VideoTextureView(mInstance.getContext());
+        mSurfaceView = new VideoSurfaceView(mInstance.getContext());
+        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.WRAP_CONTENT);
+        mTextureView.setLayoutParams(params);
+        mSurfaceView.setLayoutParams(params);
+        mTextureView.setSurfaceListener(this);
+        mSurfaceView.setSurfaceListener(this);
+
+        // TODO: Choose TextureView when SurfaceView cannot be created.
+        // Choose surface view by default
+        mTextureView.setVisibility(View.GONE);
+        mSurfaceView.setVisibility(View.VISIBLE);
+        mInstance.addView(mTextureView);
+        mInstance.addView(mSurfaceView);
+        mCurrentView = mSurfaceView;
+
+        LayoutParams subtitleParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT);
+        mSubtitleView = new SubtitleView(mInstance.getContext());
+        mSubtitleView.setLayoutParams(subtitleParams);
+        mSubtitleView.setBackgroundColor(0);
+        mInstance.addView(mSubtitleView);
+
+        // Create MediaSession
+        mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+        mMediaSession.setCallback(new MediaSessionCallback());
+
+        // TODO: Need a common namespace for attributes those are defined in updatable library.
+        boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
+                "http://schemas.android.com/apk/com.android.media.api_provider",
+                "enableControlView", true);
+        if (enableControlView) {
+            setMediaControlView2_impl(new MediaControlView2(mInstance.getContext()));
+        }
+    }
+
+    @Override
+    public void setMediaControlView2_impl(MediaControlView2 mediaControlView) {
+        mMediaControlView = mediaControlView;
+
+        // TODO: change this so that the CC button appears only where there is a subtitle track.
+        mMediaControlView.showCCButton();
+
+        // Get MediaController from MediaSession and set it inside MediaControlView2
+        mMediaControlView.setController(mMediaSession.getController());
+
+        LayoutParams params =
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+        mInstance.addView(mMediaControlView, params);
+    }
+
+    @Override
+    public MediaControlView2 getMediaControlView2_impl() {
+        return mMediaControlView;
     }
 
     @Override
     public void start_impl() {
-        // TODO: Implement
+        if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+            applySpeed();
+            mMediaPlayer.start();
+            mCurrentState = STATE_PLAYING;
+            updatePlaybackState();
+        }
+        mTargetState = STATE_PLAYING;
+        if (DEBUG) {
+            Log.d(TAG, "start(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
     }
 
     @Override
     public void pause_impl() {
-        // TODO: Implement
+        if (isInPlaybackState()) {
+            if (mMediaPlayer.isPlaying()) {
+                mMediaPlayer.pause();
+                mCurrentState = STATE_PAUSED;
+                updatePlaybackState();
+            }
+        }
+        mTargetState = STATE_PAUSED;
+        if (DEBUG) {
+            Log.d(TAG, "pause(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
     }
 
     @Override
     public int getDuration_impl() {
-        // TODO: Implement
+        if (isInPlaybackState()) {
+            return mMediaPlayer.getDuration();
+        }
         return -1;
     }
 
     @Override
     public int getCurrentPosition_impl() {
-        // TODO: Implement
+        if (isInPlaybackState()) {
+            return mMediaPlayer.getCurrentPosition();
+        }
         return 0;
     }
 
     @Override
     public void seekTo_impl(int msec) {
-        // TODO: Implement
+        if (isInPlaybackState()) {
+            mMediaPlayer.seekTo(msec);
+            mSeekWhenPrepared = 0;
+            updatePlaybackState();
+        } else {
+            mSeekWhenPrepared = msec;
+        }
     }
 
     @Override
     public boolean isPlaying_impl() {
-        // TODO: Implement
-        return false;
+        return (isInPlaybackState()) && mMediaPlayer.isPlaying();
     }
 
     @Override
     public int getBufferPercentage_impl() {
-        return -1;
+        return mCurrentBufferPercentage;
     }
 
     @Override
     public int getAudioSessionId_impl() {
-        // TODO: Implement
-        return 0;
+        if (mAudioSession == 0) {
+            MediaPlayer foo = new MediaPlayer();
+            mAudioSession = foo.getAudioSessionId();
+            foo.release();
+        }
+        return mAudioSession;
     }
 
     @Override
     public void showSubtitle_impl() {
-        // TODO: Implement
+        // Retrieve all tracks that belong to the current video.
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+
+        List<Integer> subtitleTrackIndices = new ArrayList<>();
+        for (int i = 0; i < trackInfos.length; ++i) {
+            int trackType = trackInfos[i].getTrackType();
+            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+                subtitleTrackIndices.add(i);
+            }
+        }
+        if (subtitleTrackIndices.size() > 0) {
+            // Select first subtitle track
+            mCCEnabled = true;
+            mSelectedTrackIndex = subtitleTrackIndices.get(0);
+            mMediaPlayer.selectTrack(mSelectedTrackIndex);
+        }
     }
 
     @Override
     public void hideSubtitle_impl() {
-        // TODO: Implement
+        if (mCCEnabled) {
+            mMediaPlayer.deselectTrack(mSelectedTrackIndex);
+            mCCEnabled = false;
+        }
+    }
+
+    @Override
+    public void setSpeed_impl(float speed) {
+        if (speed <= 0.0f) {
+            Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
+            return;
+        }
+        mSpeed = speed;
+        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+            applySpeed();
+        }
+    }
+
+    @Override
+    public float getSpeed_impl() {
+        if (DEBUG) {
+            if (mMediaPlayer != null) {
+                float speed = mMediaPlayer.getPlaybackParams().getSpeed();
+                if (speed != mSpeed) {
+                    Log.w(TAG, "VideoView2's speed : " + mSpeed + " is different from "
+                            + "MediaPlayer's speed : " + speed);
+                }
+            }
+        }
+        return mSpeed;
     }
 
     @Override
     public void setAudioFocusRequest_impl(int focusGain) {
-        // TODO: Implement
+        if (focusGain != AudioManager.AUDIOFOCUS_NONE
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+                && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
+            throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
+        }
+        mAudioFocusType = focusGain;
     }
 
     @Override
     public void setAudioAttributes_impl(AudioAttributes attributes) {
-        // TODO: Implement
+        if (attributes == null) {
+            throw new IllegalArgumentException("Illegal null AudioAttributes");
+        }
+        mAudioAttributes = attributes;
     }
 
     @Override
     public void setVideoPath_impl(String path) {
-        // TODO: Implement
+        mInstance.setVideoURI(Uri.parse(path));
     }
 
     @Override
     public void setVideoURI_impl(Uri uri) {
-        // TODO: Implement
+        mInstance.setVideoURI(uri, null);
     }
 
     @Override
     public void setVideoURI_impl(Uri uri, Map<String, String> headers) {
-        // TODO: Implement
-    }
-
-    @Override
-    public void setMediaController2_impl(MediaController2 controllerView) {
-        // TODO: Implement
+        mSeekWhenPrepared = 0;
+        openVideo(uri, headers);
     }
 
     @Override
     public void setViewType_impl(int viewType) {
-        // TODO: Implement
+        if (viewType == mCurrentView.getViewType()) {
+            return;
+        }
+        VideoViewInterface targetView;
+        if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) {
+            Log.d(TAG, "switching to TextureView");
+            targetView = mTextureView;
+        } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) {
+            Log.d(TAG, "switching to SurfaceView");
+            targetView = mSurfaceView;
+        } else {
+            throw new IllegalArgumentException("Unknown view type: " + viewType);
+        }
+        ((View) targetView).setVisibility(View.VISIBLE);
+        targetView.takeOver(mCurrentView);
+        mInstance.requestLayout();
     }
 
     @Override
     public int getViewType_impl() {
-        // TODO: Implement
-        return -1;
+        return mCurrentView.getViewType();
     }
 
     @Override
     public void stopPlayback_impl() {
-        // TODO: Implement
+        resetPlayer();
     }
 
     @Override
-    public void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l) {
-        // TODO: Implement
+    public void setOnPreparedListener_impl(VideoView2.OnPreparedListener l) {
+        mOnPreparedListener = l;
     }
 
     @Override
-    public void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l) {
-        // TODO: Implement
+    public void setOnCompletionListener_impl(VideoView2.OnCompletionListener l) {
+        mOnCompletionListener = l;
     }
 
     @Override
-    public void setOnErrorListener_impl(MediaPlayer.OnErrorListener l) {
-        // TODO: Implement
+    public void setOnErrorListener_impl(VideoView2.OnErrorListener l) {
+        mOnErrorListener = l;
     }
 
     @Override
-    public void setOnInfoListener_impl(MediaPlayer.OnInfoListener l) {
-        // TODO: Implement
+    public void setOnInfoListener_impl(VideoView2.OnInfoListener l) {
+        mOnInfoListener = l;
     }
 
     @Override
     public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
-        // TODO: Implement
-    }
-
-    @Override
-    public void onAttachedToWindow_impl() {
-        mSuperProvider.onAttachedToWindow_impl();
-        // TODO: Implement
-    }
-
-    @Override
-    public void onDetachedFromWindow_impl() {
-        mSuperProvider.onDetachedFromWindow_impl();
-        // TODO: Implement
-    }
-
-    @Override
-    public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
-        mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
-        // TODO: Implement
-    }
-
-    @Override
-    public void draw_impl(Canvas canvas) {
-        mSuperProvider.draw_impl(canvas);
-        // TODO: Implement
+        mOnViewTypeChangedListener = l;
     }
 
     @Override
     public CharSequence getAccessibilityClassName_impl() {
-        // TODO: Implement
-        return null;
+        return VideoView2.class.getName();
     }
 
     @Override
     public boolean onTouchEvent_impl(MotionEvent ev) {
-        // TODO: Implement
-        return false;
+        if (DEBUG) {
+            Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState);
+        }
+        if (ev.getAction() == MotionEvent.ACTION_UP
+                && isInPlaybackState() && mMediaControlView != null) {
+            toggleMediaControlViewVisibility();
+        }
+        return mSuperProvider.onTouchEvent_impl(ev);
     }
 
     @Override
     public boolean onTrackballEvent_impl(MotionEvent ev) {
-        // TODO: Implement
-        return false;
+        if (ev.getAction() == MotionEvent.ACTION_UP
+                && isInPlaybackState() && mMediaControlView != null) {
+            toggleMediaControlViewVisibility();
+        }
+        return mSuperProvider.onTrackballEvent_impl(ev);
     }
 
     @Override
     public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
-        // TODO: Implement
-        return false;
+        Log.v(TAG, "onKeyDown_impl: " + keyCode);
+        boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
+                && keyCode != KeyEvent.KEYCODE_VOLUME_UP
+                && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
+                && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
+                && keyCode != KeyEvent.KEYCODE_MENU
+                && keyCode != KeyEvent.KEYCODE_CALL
+                && keyCode != KeyEvent.KEYCODE_ENDCALL;
+        if (isInPlaybackState() && isKeyCodeSupported && mMediaControlView != null) {
+            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+                    || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+                if (mMediaPlayer.isPlaying()) {
+                    mInstance.pause();
+                    mMediaControlView.show();
+                } else {
+                    mInstance.start();
+                    mMediaControlView.hide();
+                }
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+                if (!mMediaPlayer.isPlaying()) {
+                    mInstance.start();
+                    mMediaControlView.hide();
+                }
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+                    || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+                if (mMediaPlayer.isPlaying()) {
+                    mInstance.pause();
+                    mMediaControlView.show();
+                }
+                return true;
+            } else {
+                toggleMediaControlViewVisibility();
+            }
+        }
+
+        return mSuperProvider.onKeyDown_impl(keyCode, event);
     }
 
     @Override
     public void onFinishInflate_impl() {
-        // TODO: Implement
+        mSuperProvider.onFinishInflate_impl();
     }
 
     @Override
     public boolean dispatchKeyEvent_impl(KeyEvent event) {
-        // TODO: Implement
-        return false;
+        return mSuperProvider.dispatchKeyEvent_impl(event);
     }
 
     @Override
     public void setEnabled_impl(boolean enabled) {
-        // TODO: Implement
+        mSuperProvider.setEnabled_impl(enabled);
     }
 
     ///////////////////////////////////////////////////
@@ -238,21 +504,484 @@
 
     @Override
     public void onSurfaceCreated(View view, int width, int height) {
-        // TODO: Implement
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
+                    + ", " + view.toString());
+        }
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
+
+        if (needToStart()) {
+            mInstance.start();
+        }
     }
 
     @Override
     public void onSurfaceDestroyed(View view) {
-        // TODO: Implement
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState
+                    + ", mTargetState=" + mTargetState + ", " + view.toString());
+        }
+        if (mMediaControlView != null) {
+            mMediaControlView.hide();
+        }
     }
 
     @Override
     public void onSurfaceChanged(View view, int width, int height) {
-        // TODO: Implement
+        // TODO: Do we need to call requestLayout here?
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
+                    + ", " + view.toString());
+        }
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
     }
 
     @Override
     public void onSurfaceTakeOverDone(VideoViewInterface view) {
-        // TODO: Implement
+        if (DEBUG) {
+            Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
+        }
+        mCurrentView = view;
+        if (mOnViewTypeChangedListener != null) {
+            mOnViewTypeChangedListener.onViewTypeChanged(view.getViewType());
+        }
+        if (needToStart()) {
+            mInstance.start();
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // Protected or private methods
+    ///////////////////////////////////////////////////
+
+    private boolean isInPlaybackState() {
+        return (mMediaPlayer != null
+                && mCurrentState != STATE_ERROR
+                && mCurrentState != STATE_IDLE
+                && mCurrentState != STATE_PREPARING);
+    }
+
+    private boolean needToStart() {
+        return (mMediaPlayer != null
+                && mCurrentState != STATE_PLAYING
+                && mTargetState == STATE_PLAYING);
+    }
+
+    // Creates a MediaPlayer instance and prepare playback.
+    private void openVideo(Uri uri, Map<String, String> headers) {
+        resetPlayer();
+        if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+            // TODO this should have a focus listener
+            AudioFocusRequest focusRequest;
+            focusRequest = new AudioFocusRequest.Builder(mAudioFocusType)
+                    .setAudioAttributes(mAudioAttributes)
+                    .build();
+            mAudioManager.requestAudioFocus(focusRequest);
+        }
+
+        try {
+            Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
+            mMediaPlayer = new MediaPlayer();
+            mSurfaceView.setMediaPlayer(mMediaPlayer);
+            mTextureView.setMediaPlayer(mMediaPlayer);
+            mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
+
+            // TODO: create SubtitleController in MediaPlayer, but we need
+            // a context for the subtitle renderers
+            final Context context = mInstance.getContext();
+            final SubtitleController controller = new SubtitleController(
+                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
+            controller.registerRenderer(new WebVttRenderer(context));
+            controller.registerRenderer(new TtmlRenderer(context));
+            controller.registerRenderer(new Cea708CaptionRenderer(context));
+            controller.registerRenderer(new ClosedCaptionRenderer(context));
+            mMediaPlayer.setSubtitleAnchor(controller, (SubtitleController.Anchor) mSubtitleView);
+
+            if (mAudioSession != 0) {
+                mMediaPlayer.setAudioSessionId(mAudioSession);
+            } else {
+                mAudioSession = mMediaPlayer.getAudioSessionId();
+            }
+            mMediaPlayer.setOnPreparedListener(mPreparedListener);
+            mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+            mMediaPlayer.setOnCompletionListener(mCompletionListener);
+            mMediaPlayer.setOnErrorListener(mErrorListener);
+            mMediaPlayer.setOnInfoListener(mInfoListener);
+            mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+            mCurrentBufferPercentage = 0;
+            mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+            mMediaPlayer.setAudioAttributes(mAudioAttributes);
+            // we don't set the target state here either, but preserve the
+            // target state that was there before.
+            mCurrentState = STATE_PREPARING;
+            mMediaPlayer.prepareAsync();
+
+            if (DEBUG) {
+                Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
+            }
+            /*
+            for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
+                try {
+                    mMediaPlayer.addSubtitleSource(pending.first, pending.second);
+                } catch (IllegalStateException e) {
+                    mInfoListener.onInfo(
+                            mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
+                }
+            }
+            */
+        } catch (IOException | IllegalArgumentException ex) {
+            Log.w(TAG, "Unable to open content: " + uri, ex);
+            mCurrentState = STATE_ERROR;
+            mTargetState = STATE_ERROR;
+            mErrorListener.onError(mMediaPlayer,
+                    MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
+        } finally {
+            //mPendingSubtitleTracks.clear();
+        }
+    }
+
+    /*
+     * Reset the media player in any state
+     */
+    // TODO: Figure out if the legacy code's boolean parameter: cleartargetstate is necessary.
+    private void resetPlayer() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+            //mPendingSubtitleTracks.clear();
+            mCurrentState = STATE_IDLE;
+            mTargetState = STATE_IDLE;
+            if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+                mAudioManager.abandonAudioFocus(null);
+            }
+        }
+        mSurfaceWidth = 0;
+        mSurfaceHeight = 0;
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+    }
+
+    private void updatePlaybackState() {
+        if (mStateBuilder == null) {
+            // Get the capabilities of the player for this stream
+            Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+                    MediaPlayer.BYPASS_METADATA_FILTER);
+
+            // Add Play action as default
+            long playbackActions = PlaybackState.ACTION_PLAY;
+            if (data != null) {
+                if (!data.has(Metadata.PAUSE_AVAILABLE)
+                        || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_PAUSE;
+                }
+                if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_REWIND;
+                }
+                if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
+                }
+                if (!data.has(Metadata.SEEK_AVAILABLE)
+                        || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+                    playbackActions |= PlaybackState.ACTION_SEEK_TO;
+                }
+            } else {
+                playbackActions |= (PlaybackState.ACTION_PAUSE |
+                        PlaybackState.ACTION_REWIND | PlaybackState.ACTION_FAST_FORWARD |
+                        PlaybackState.ACTION_SEEK_TO);
+            }
+            mStateBuilder = new PlaybackState.Builder();
+            mStateBuilder.setActions(playbackActions);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
+            mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+        }
+        mStateBuilder.setState(getCorrespondingPlaybackState(),
+                mInstance.getCurrentPosition(), 1.0f);
+        mStateBuilder.setBufferedPosition(
+                (long) (mCurrentBufferPercentage / 100.0) * mInstance.getDuration());
+
+        // Set PlaybackState for MediaSession
+        if (mMediaSession != null) {
+            PlaybackState state = mStateBuilder.build();
+            mMediaSession.setPlaybackState(state);
+        }
+    }
+
+    private int getCorrespondingPlaybackState() {
+        switch (mCurrentState) {
+            case STATE_ERROR:
+                return PlaybackState.STATE_ERROR;
+            case STATE_IDLE:
+                return PlaybackState.STATE_NONE;
+            case STATE_PREPARING:
+                return PlaybackState.STATE_CONNECTING;
+            case STATE_PREPARED:
+                return PlaybackState.STATE_STOPPED;
+            case STATE_PLAYING:
+                return PlaybackState.STATE_PLAYING;
+            case STATE_PAUSED:
+                return PlaybackState.STATE_PAUSED;
+            case STATE_PLAYBACK_COMPLETED:
+                return PlaybackState.STATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    private void toggleMediaControlViewVisibility() {
+        if (mMediaControlView.isShowing()) {
+            mMediaControlView.hide();
+        } else {
+            mMediaControlView.show();
+        }
+    }
+
+    private void applySpeed() {
+        PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults();
+        if (mSpeed != params.getSpeed()) {
+            try {
+                params.setSpeed(mSpeed);
+                mMediaPlayer.setPlaybackParams(params);
+                mFallbackSpeed = mSpeed;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "PlaybackParams has unsupported value: " + e);
+                // TODO: should revise this part after integrating with MP2.
+                // If mSpeed had an illegal value for speed rate, system will determine best
+                // handling (see PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT).
+                // Note: The pre-MP2 returns 0.0f when it is paused. In this case, VideoView2 will
+                // use mFallbackSpeed instead.
+                float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed();
+                if (fallbackSpeed > 0.0f) {
+                    mFallbackSpeed = fallbackSpeed;
+                }
+                mSpeed = mFallbackSpeed;
+            }
+        }
+    }
+
+    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+            new MediaPlayer.OnVideoSizeChangedListener() {
+                public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+                    if (DEBUG) {
+                        Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+                    }
+                    mVideoWidth = mp.getVideoWidth();
+                    mVideoHeight = mp.getVideoHeight();
+                    if (DEBUG) {
+                        Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+                                + mVideoHeight);
+                    }
+
+                    if (mVideoWidth != 0 && mVideoHeight != 0) {
+                        mInstance.requestLayout();
+                    }
+                }
+            };
+
+    MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+        public void onPrepared(MediaPlayer mp) {
+            if (DEBUG) {
+                Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+                        + ", mTargetState=" + mTargetState);
+            }
+            mCurrentState = STATE_PREPARED;
+            if (mOnPreparedListener != null) {
+                mOnPreparedListener.onPrepared();
+            }
+            if (mMediaControlView != null) {
+                mMediaControlView.setEnabled(true);
+            }
+            mVideoWidth = mp.getVideoWidth();
+            mVideoHeight = mp.getVideoHeight();
+
+            // mSeekWhenPrepared may be changed after seekTo() call
+            int seekToPosition = mSeekWhenPrepared;
+            if (seekToPosition != 0) {
+                mInstance.seekTo(seekToPosition);
+            }
+
+            // Create and set playback state for MediaControlView2
+            updatePlaybackState();
+
+            // Get and set duration value as MediaMetadata for MediaControlView2
+            MediaMetadata.Builder builder = new MediaMetadata.Builder();
+            builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
+            if (mMediaSession != null) {
+                mMediaSession.setMetadata(builder.build());
+            }
+
+            if (mVideoWidth != 0 && mVideoHeight != 0) {
+                if (mVideoWidth != mSurfaceWidth || mVideoHeight != mSurfaceHeight) {
+                    if (DEBUG) {
+                        Log.i(TAG, "OnPreparedListener() : ");
+                        Log.i(TAG, " video size: " + mVideoWidth + "/" + mVideoHeight);
+                        Log.i(TAG, " surface size: " + mSurfaceWidth + "/" + mSurfaceHeight);
+                        Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+                                + mInstance.getMeasuredHeight());
+                        Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+                                + mInstance.getHeight());
+                    }
+
+                    // TODO: It seems like that overriding onMeasure() is needed like legacy code.
+                    mSurfaceWidth = mVideoWidth;
+                    mSurfaceHeight = mVideoHeight;
+                    mInstance.requestLayout();
+                }
+                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
+                    // We didn't actually change the size (it was already at the size
+                    // we need), so we won't get a "surface changed" callback, so
+                    // start the video here instead of in the callback.
+                    if (needToStart()) {
+                        mInstance.start();
+                        if (mMediaControlView != null) {
+                            mMediaControlView.show();
+                        }
+                    } else if (!mInstance.isPlaying() && (seekToPosition != 0
+                            || mInstance.getCurrentPosition() > 0)) {
+                        if (mMediaControlView != null) {
+                            // Show the media controls when we're paused into a video and
+                            // make them stick.
+                            mMediaControlView.show(0);
+                        }
+                    }
+                }
+            } else {
+                // We don't know the video size yet, but should start anyway.
+                // The video size might be reported to us later.
+                if (needToStart()) {
+                    mInstance.start();
+                }
+            }
+        }
+    };
+
+    private MediaPlayer.OnCompletionListener mCompletionListener =
+            new MediaPlayer.OnCompletionListener() {
+                public void onCompletion(MediaPlayer mp) {
+                    mCurrentState = STATE_PLAYBACK_COMPLETED;
+                    mTargetState = STATE_PLAYBACK_COMPLETED;
+                    updatePlaybackState();
+
+                    if (mMediaControlView != null) {
+                        mMediaControlView.hide();
+                    }
+                    if (mOnCompletionListener != null) {
+                        mOnCompletionListener.onCompletion();
+                    }
+                    if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+                        mAudioManager.abandonAudioFocus(null);
+                    }
+                }
+            };
+
+    private MediaPlayer.OnInfoListener mInfoListener =
+            new MediaPlayer.OnInfoListener() {
+                public boolean onInfo(MediaPlayer mp, int what, int extra) {
+                    if (mOnInfoListener != null) {
+                        mOnInfoListener.onInfo(what, extra);
+                    }
+                    return true;
+                }
+            };
+
+    private MediaPlayer.OnErrorListener mErrorListener =
+            new MediaPlayer.OnErrorListener() {
+                public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
+                    }
+                    mCurrentState = STATE_ERROR;
+                    mTargetState = STATE_ERROR;
+                    updatePlaybackState();
+
+                    if (mMediaControlView != null) {
+                        mMediaControlView.hide();
+                    }
+
+                    /* If an error handler has been supplied, use it and finish. */
+                    if (mOnErrorListener != null) {
+                        if (mOnErrorListener.onError(frameworkErr, implErr)) {
+                            return true;
+                        }
+                    }
+
+                    /* Otherwise, pop up an error dialog so the user knows that
+                     * something bad has happened. Only try and pop up the dialog
+                     * if we're attached to a window. When we're going away and no
+                     * longer have a window, don't bother showing the user an error.
+                    */
+                    if (mInstance.getWindowToken() != null) {
+                        int messageId;
+
+                        if (frameworkErr
+                                == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+                            messageId = R.string.VideoView2_error_text_invalid_progressive_playback;
+                        } else {
+                            messageId = R.string.VideoView2_error_text_unknown;
+                        }
+
+                        Resources res = ApiHelper.getLibResources();
+                        new AlertDialog.Builder(mInstance.getContext())
+                                .setMessage(res.getString(messageId))
+                                .setPositiveButton(res.getString(R.string.VideoView2_error_button),
+                                        new DialogInterface.OnClickListener() {
+                                            public void onClick(DialogInterface dialog,
+                                                                int whichButton) {
+                                                /* If we get here, there is no onError listener, so
+                                                * at least inform them that the video is over.
+                                                */
+                                                if (mOnCompletionListener != null) {
+                                                    mOnCompletionListener.onCompletion();
+                                                }
+                                            }
+                                        })
+                                .setCancelable(false)
+                                .show();
+                    }
+                    return true;
+                }
+            };
+
+    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
+            new MediaPlayer.OnBufferingUpdateListener() {
+                public void onBufferingUpdate(MediaPlayer mp, int percent) {
+                    mCurrentBufferPercentage = percent;
+                    updatePlaybackState();
+                }
+            };
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+        @Override
+        public void onCommand(String command, Bundle args, ResultReceiver receiver) {
+            switch (command) {
+                case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
+                    mInstance.showSubtitle();
+                    break;
+                case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
+                    mInstance.hideSubtitle();
+                    break;
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            mInstance.start();
+        }
+
+        @Override
+        public void onPause() {
+            mInstance.pause();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mInstance.seekTo((int) pos);
+        }
     }
 }