Merge "Add AndroidTV prebuilds for LMP Preview release" into lmp-preview-dev
diff --git a/prebuilts/androidtv/leanback/AndroidManifest.xml b/prebuilts/androidtv/leanback/AndroidManifest.xml
new file mode 100644
index 0000000..e3bf401
--- /dev/null
+++ b/prebuilts/androidtv/leanback/AndroidManifest.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.leanback"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@drawable/videos_by_google_banner"
+        android:label="@string/app_name"
+        android:logo="@drawable/videos_by_google_banner"
+        android:theme="@style/Theme.Leanback" >
+        <activity
+            android:name="MainActivity"
+            android:icon="@drawable/videos_by_google_banner"
+            android:label="@string/app_name"
+            android:logo="@drawable/videos_by_google_banner"
+            android:screenOrientation="landscape" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="DetailsActivity"
+            android:exported="true" />
+        <activity
+            android:name="PlayerActivity"
+            android:exported="true"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+        <activity
+            android:name="VerticalGridActivity"
+            android:exported="true"
+            android:parentActivityName="MainActivity" />
+        <activity
+            android:name="SearchActivity"
+            android:exported="true" />
+
+        <receiver
+            android:name=".BootupActivity"
+            android:enabled="true"
+            android:exported="false" >
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+
+        <service
+            android:name=".UpdateRecommendationsService"
+            android:enabled="true" />
+    </application>
+
+</manifest>
diff --git a/prebuilts/androidtv/leanback/CONTRIBUTING.md b/prebuilts/androidtv/leanback/CONTRIBUTING.md
new file mode 100644
index 0000000..59e2c2f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we 
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement
+(CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (http://code.google.com/legal/individual-cla-v1.0.html).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (http://code.google.com/legal/corporate-cla-v1.0.html).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing a Patch
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your change to the repo in question.
+    * Fork the desired repo, develop and test your code changes.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+1. The repo owner will review your request. If it is approved, the change will
+   be merged. If it needs additional work, the repo owner will respond with
+   useful comments.
+
+## Contributing a New Sample App
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your own repo for your app following this naming convention:
+    * mirror-{app-name}-{language or plaform}
+    * apps: quickstart, photohunt-server, photohunt-client
+    * example:  mirror-quickstart-android
+    * For multi-language apps, concatenate the primary languages like this:
+      mirror-photohunt-server-java-python.
+
+1. Create your sample app in this repo.
+    * Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
+      googlecast repo.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+    * Instructional value is the top priority when evaluating new app proposals for
+      this collection of repos.
+1. Submit a request to fork your repo in googlecast organization.
+1. The repo owner will review your request. If it is approved, the sample will
+   be merged. If it needs additional work, the repo owner will respond with 
+   useful comments.
diff --git a/prebuilts/androidtv/leanback/LICENSE b/prebuilts/androidtv/leanback/LICENSE
new file mode 100644
index 0000000..8405e89
--- /dev/null
+++ b/prebuilts/androidtv/leanback/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/README.md b/prebuilts/androidtv/leanback/README.md
new file mode 100644
index 0000000..320e4f2
--- /dev/null
+++ b/prebuilts/androidtv/leanback/README.md
@@ -0,0 +1,26 @@
+# VisualGameController
+
+The Leanback API Demo/Video By Googles app is designed to run on an Android TV device and demonstrates how to use the Leanback Support library
+in order to comply with the UX guidelines of Android TV.
+
+## Dependencies
+* Android SDK v7 appcompat library
+* Android SDK v17 leanback support library
+* Android SDK v7 recyclerview library
+
+## Setup Instructions
+* Compile and deploy to your Android TV device.
+
+## References and How to report bugs
+* [Developer Documentation](http://developers.google.com/)
+
+## How to make contributions?
+Please read and follow the steps in the CONTRIBUTING.md
+
+## License
+See LICENSE
+
+## Google+
+Google Developers Page on Google+ [https://plus.google.com/+GoogleDevelopers/posts](https://plus.google.com/+GoogleDevelopers/posts)
+
+## Change List
diff --git a/prebuilts/androidtv/leanback/project.properties b/prebuilts/androidtv/leanback/project.properties
new file mode 100644
index 0000000..70b2922
--- /dev/null
+++ b/prebuilts/androidtv/leanback/project.properties
@@ -0,0 +1,16 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library.reference.1=../../1229585_support/v17/leanback
+android.library.reference.2=../../1229585_support/v7/appcompat
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum.png
new file mode 100644
index 0000000..fda9a74
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..498cf66
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_av_play_dark.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_av_play_dark.png
new file mode 100644
index 0000000..c9c7828
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_av_play_dark.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..4cedb52
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_banner.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..20fd898
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_icon.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png
new file mode 100644
index 0000000..6b62138
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..ac9cc30
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_av_pause_dark.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_av_pause_dark.png
new file mode 100644
index 0000000..6270d65
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_av_pause_dark.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..b191626
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_banner.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..8a7c6dc
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_icon.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png
new file mode 100644
index 0000000..825ef63
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..9b1703d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/card_background_default.9.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/card_background_default.9.png
new file mode 100644
index 0000000..29f4e01
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/card_background_default.9.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/default_background.xml b/prebuilts/androidtv/leanback/res/drawable-xhdpi/default_background.xml
new file mode 100644
index 0000000..07b0589
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/default_background.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <gradient
+            android:startColor="@color/background_gradient_start"
+            android:endColor="@color/background_gradient_end"
+            android:angle="-270" />
+</shape>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/grid_bg.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/grid_bg.png
new file mode 100644
index 0000000..476c698
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/grid_bg.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png
new file mode 100644
index 0000000..63b45b9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_normal.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_normal.png
new file mode 100644
index 0000000..9cf2582
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_normal.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png
new file mode 100644
index 0000000..516ceca
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_focussed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_focussed.png
new file mode 100644
index 0000000..bf93814
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_focussed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_normal.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_normal.png
new file mode 100644
index 0000000..966f754
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_normal.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_pressed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_pressed.png
new file mode 100644
index 0000000..934ed89
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_pressed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_focussed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_focussed.png
new file mode 100644
index 0000000..687b421
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_focussed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_normal.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_normal.png
new file mode 100644
index 0000000..1eadfa4
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_normal.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_pressed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_pressed.png
new file mode 100644
index 0000000..7f583f6
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_pressed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png
new file mode 100644
index 0000000..9bb7247
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png
new file mode 100644
index 0000000..2289859
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png
new file mode 100644
index 0000000..34aff7c
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png
new file mode 100644
index 0000000..3c492e1
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png
new file mode 100644
index 0000000..6d00d09
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..bdcf41e
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_banner.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..9bc4836
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_icon.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png
new file mode 100644
index 0000000..c82f94c
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..6c50e8f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..6c121e6
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_banner.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..4258160
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_icon.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png
new file mode 100644
index 0000000..fda9a74
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png
new file mode 100644
index 0000000..498cf66
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/details_img.png b/prebuilts/androidtv/leanback/res/drawable/details_img.png
new file mode 100644
index 0000000..7ea688b
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/details_img.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png b/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png
new file mode 100644
index 0000000..3d555ef
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/ic_title.png b/prebuilts/androidtv/leanback/res/drawable/ic_title.png
new file mode 100644
index 0000000..1c62b2e
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/ic_title.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/movie.png b/prebuilts/androidtv/leanback/res/drawable/movie.png
new file mode 100644
index 0000000..cb5cb6d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/movie.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/player_bg_gradient_dark.xml b/prebuilts/androidtv/leanback/res/drawable/player_bg_gradient_dark.xml
new file mode 100644
index 0000000..4450cb6
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/player_bg_gradient_dark.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <gradient
+        android:angle="90"
+        android:centerColor="#00000000"
+        android:endColor="#B2000000"
+        android:startColor="#B2000000"
+        android:type="linear" />
+
+</shape>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/drawable/shadow7.9.png b/prebuilts/androidtv/leanback/res/drawable/shadow7.9.png
new file mode 100644
index 0000000..6d00d09
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/shadow7.9.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png
new file mode 100644
index 0000000..4cedb52
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png
new file mode 100644
index 0000000..20fd898
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/res/layout/details.xml b/prebuilts/androidtv/leanback/res/layout/details.xml
new file mode 100644
index 0000000..e7f675d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/details.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="com.example.android.leanback.LeanbackDetailsFragment"
+    android:id="@+id/details_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
diff --git a/prebuilts/androidtv/leanback/res/layout/main.xml b/prebuilts/androidtv/leanback/res/layout/main.xml
new file mode 100644
index 0000000..46eaad9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/main.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="com.example.android.leanback.MainFragment"
+    android:id="@+id/main_browse_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
diff --git a/prebuilts/androidtv/leanback/res/layout/movie_card.xml b/prebuilts/androidtv/leanback/res/layout/movie_card.xml
new file mode 100644
index 0000000..29bad93
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/movie_card.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_height="wrap_content"
+              android:layout_width="wrap_content"
+              android:scaleType="centerCrop"
+              android:focusable="true"
+              android:focusableInTouchMode="true">
+    <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:id="@+id/poster" android:layout_gravity="center"/>
+    <TextView
+       android:id="@+id/poster_text"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:paddingTop="10dp"
+       android:paddingRight="10dp"       
+       android:paddingBottom="10dp"
+       android:paddingLeft="10dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/layout/player_activity.xml b/prebuilts/androidtv/leanback/res/layout/player_activity.xml
new file mode 100644
index 0000000..f976f7f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/player_activity.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <VideoView android:id="@+id/videoView"
+             android:layout_width="fill_parent"
+             android:layout_alignParentRight="true"
+             android:layout_alignParentLeft="true"
+             android:layout_alignParentTop="true"
+             android:layout_alignParentBottom="true"
+             android:layout_height="fill_parent"
+             android:layout_gravity="center"
+             android:layout_centerInParent="true">
+    </VideoView>
+    
+    <RelativeLayout
+        android:id="@+id/controllers"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@+id/videoView"
+        android:layout_alignLeft="@+id/videoView"
+        android:layout_alignRight="@+id/videoView"
+        android:layout_alignTop="@+id/videoView"
+        android:layout_centerInParent="true"
+        android:background="@drawable/player_bg_gradient_dark" >
+
+        <ProgressBar
+            android:id="@+id/progressBar"
+            style="@android:style/Widget.ProgressBar.Horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:visibility="gone" />
+
+        <RelativeLayout
+            android:layout_width="fill_parent"
+            android:layout_height="45dp"
+            android:layout_alignParentBottom="true" >
+
+            <ImageView
+                android:id="@+id/playpause"
+                android:contentDescription="@+id/play_pause_description"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:src="@drawable/ic_play_playcontrol_normal" />
+
+            <TextView
+                android:id="@+id/startText"
+                android:layout_width="wrap_content"
+                android:layout_height="fill_parent"
+                android:layout_marginLeft="5dp"
+                android:layout_toRightOf="@+id/playpause"
+                android:gravity="center_vertical"
+                android:maxLines="1"
+                android:text="@+id/init_text"
+                android:textColor="@color/white" />
+
+            <TextView
+                android:id="@+id/endText"
+                android:layout_width="wrap_content"
+                android:layout_height="fill_parent"
+                android:layout_alignParentRight="true"
+                android:layout_marginRight="16dp"
+                android:gravity="center_vertical"
+                android:maxLines="1"
+                android:text="@+id/init_text"
+                android:textColor="@color/white" />
+
+            <SeekBar
+                android:id="@+id/seekBar"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_gravity="center"
+                android:layout_marginLeft="5dp"
+                android:layout_marginRight="5dp"
+                android:layout_toLeftOf="@+id/endText"
+                android:layout_toRightOf="@+id/startText" />
+        </RelativeLayout>
+    </RelativeLayout>
+
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/layout/search.xml b/prebuilts/androidtv/leanback/res/layout/search.xml
new file mode 100644
index 0000000..b65600c
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/search.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+          android:name="com.example.android.leanback.SearchFragment"
+          android:id="@+id/search_fragment"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+        />
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/layout/vertical_grid.xml b/prebuilts/androidtv/leanback/res/layout/vertical_grid.xml
new file mode 100644
index 0000000..1007042
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/layout/vertical_grid.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="com.example.android.leanback.VerticalGridFragment"
+    android:id="@+id/vertical_grid_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
diff --git a/prebuilts/androidtv/leanback/res/values/colors.xml b/prebuilts/androidtv/leanback/res/values/colors.xml
new file mode 100644
index 0000000..1583f32
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="background_gradient_start">#000000</color>
+    <color name="background_gradient_end">#DDDDDD</color>
+    <color name="fastlane_background">#0096a6</color>
+    <color name="search_opaque">#ffaa3f</color>
+    <color name="detail_background">#0096a6</color>
+    <color name="soft_opaque">#30000000</color>
+    <color name="img_soft_opaque">#30FF0000</color>
+    <color name="img_full_opaque">#00000000</color>
+    <color name="black_opaque">#AA000000</color>
+    <color name="black">#59000000</color>
+    <color name="white">#FFFFFF</color>
+    <color name="orange_transparent">#AAFADCA7</color>
+    <color name="orange">#FADCA7</color>
+    <color name="yellow">#EEFF41</color>
+    <color name="default_background">#3d3d3d</color>
+</resources>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/res/values/strings.xml b/prebuilts/androidtv/leanback/res/values/strings.xml
new file mode 100644
index 0000000..3e47d39
--- /dev/null
+++ b/prebuilts/androidtv/leanback/res/values/strings.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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>
+    <string name="app_name">Leanback API Demo</string>
+    <string name="browse_title"><![CDATA[Videos by Google]]></string>
+    <string name="related_movies">Related Movies</string>
+    <string name="vertical_grid_title"><![CDATA[Vertical Video Grid]]></string>
+    <string name="error">Error</string>
+    <string name="ok">OK</string>
+    <string name="pause">Pause</string>
+    <string name="play">Play</string>
+    <string name="stop">Stop</string>
+    <string name="init_text">00:00</string>
+    <string name="play_pause_description">Play Pause Button</string>
+    <string name="loading">Loading&#8230;</string>
+    <string name="no_video_found">No video was found</string>
+    <string name="version">Version: %1$s</string>
+    <string name="popular_header">Popular Videos</string>
+    <string name="preferences">PREFERENCES</string>
+    <string name="grid_view">Grid View</string>
+    <string name="send_feeback">Send Feedback</string>
+    <string name="personal_settings">Personal Settings</string>
+    <string name="watch_trailer_1">Watch trailer</string>
+    <string name="watch_trailer_2">FREE</string>
+    <string name="rent_1">Rent By Day</string>
+    <string name="rent_2">From $1.99</string>
+    <string name="buy_1">Buy and Own</string>
+    <string name="buy_2">AT $9.99</string>
+    <string name="movie">Movie</string>
+    <string name="should_start">shouldStart</string>
+    <string name="start_position">startPosition</string>
+    <string name="search_results">Search Results</string>
+    <string name="catalog_url">http://commondatastorage.googleapis.com/android-tv/android_tv_videos.json</string>
+    <string name="prefix_url">http://commondatastorage.googleapis.com/android-tv/Sample%20videos/</string>
+    
+    <!-- Error messages -->
+    <string name="failed_to_launch_app">Failed to launch application</string>
+    <string name="failed_to_find_app">The application you are trying to launch is not available</string>
+    <string name="failed_app_launch_timeout">The request to launch the application has timed out!</string>
+    <string name="failed_to_play">Failed to start the playback of media</string>
+    <string name="failed_to_pause">Failed to pause the playback of media</string>
+    <string name="failed_to_connect">Could not connect to the device</string>
+    <string name="failed_to_seek">Failed to seek to the specified position on the remote device</string>
+    <string name="video_error_media_load_timeout">Media loading timed out</string>
+    <string name="video_error_server_unaccessible">Media server was not reachable</string>
+    <string name="video_error_unknown_error">Failed to load video</string>
+    <string name="oops">Oops</string>
+
+    <!-- Preferences -->
+    <string name="prefs_header_application">Application Behavior</string>
+    <string name="prefs_header_application_summary">Control how the application behaves on the TV</string>
+    <string name="prefs_termination_policy_default">0</string>
+    <string name="prefs_termination_policy_dialog_title">When Disconnecting</string>
+    <string name="prefs_volume_title">Volume Assignment</string>
+    <string name="prefs_volume_title_summary">Controls %1$s</string>
+    <string name="prefs_volume_dialog_title">Volume Assignment</string>
+    <string name="prefs_volume_default">device</string>
+    <string name="title_activity_test">TestActivity</string>
+</resources>
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/BootupActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/BootupActivity.java
new file mode 100644
index 0000000..4e35e3d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/BootupActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/*
+ * This class extends BroadCastReceiver and publishes recommendations on bootup 
+ */
+public class BootupActivity extends BroadcastReceiver {
+    private static final String TAG = "BootupActivity";
+
+    private static final long INITIAL_DELAY = 5000;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "BootupActivity initiated");
+        if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
+            scheduleRecommendationUpdate(context);
+        }
+    }
+
+    private void scheduleRecommendationUpdate(Context context) {
+        Log.d(TAG, "Scheduling recommendations update");
+
+        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
+        PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);
+
+        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                INITIAL_DELAY,
+                AlarmManager.INTERVAL_HALF_HOUR,
+                alarmIntent);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/CardPresenter.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/CardPresenter.java
new file mode 100644
index 0000000..00afac5
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/CardPresenter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.Presenter;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import java.net.URI;
+
+/*
+ * A CardPresenter is used to generate Views and bind Objects to them on demand. 
+ * It contains an Image CardView
+ */
+public class CardPresenter extends Presenter {
+    private static final String TAG = "CardPresenter";
+
+    private static Context mContext;
+    private static int CARD_WIDTH = 313;
+    private static int CARD_HEIGHT = 176;
+
+    static class ViewHolder extends Presenter.ViewHolder {
+        private Movie mMovie;
+        private ImageCardView mCardView;
+        private Drawable mDefaultCardImage;
+        private PicassoImageCardViewTarget mImageCardViewTarget;
+
+        public ViewHolder(View view) {
+            super(view);
+            mCardView = (ImageCardView) view;
+            mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
+            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
+        }
+
+        public void setMovie(Movie m) {
+            mMovie = m;
+        }
+
+        public Movie getMovie() {
+            return mMovie;
+        }
+
+        public ImageCardView getCardView() {
+            return mCardView;
+        }
+
+        protected void updateCardViewImage(URI uri) {
+            Picasso.with(mContext)
+                    .load(uri.toString())
+                    .resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
+                    .error(mDefaultCardImage)
+                    .into(mImageCardViewTarget);
+        }
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        Log.d(TAG, "onCreateViewHolder");
+        mContext = parent.getContext();
+
+        ImageCardView cardView = new ImageCardView(mContext);
+        cardView.setFocusable(true);
+        cardView.setFocusableInTouchMode(true);
+        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
+        return new ViewHolder(cardView);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        Movie movie = (Movie) item;
+        ((ViewHolder) viewHolder).setMovie(movie);
+
+        Log.d(TAG, "onBindViewHolder");
+        if (movie.getCardImageUrl() != null) {
+            ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
+            ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
+            ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
+            ((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
+        }
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        Log.d(TAG, "onUnbindViewHolder");
+    }
+
+    @Override
+    public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
+        // TO DO
+    }
+
+    public static class PicassoImageCardViewTarget implements Target {
+        private ImageCardView mImageCardView;
+
+        public PicassoImageCardViewTarget(ImageCardView imageCardView) {
+            mImageCardView = imageCardView;
+        }
+
+        @Override
+        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
+            Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+            mImageCardView.setMainImage(bitmapDrawable);
+        }
+
+        @Override
+        public void onBitmapFailed(Drawable drawable) {
+            mImageCardView.setMainImage(drawable);
+        }
+
+        @Override
+        public void onPrepareLoad(Drawable drawable) {
+            // Do nothing, default_background manager has its own transitions
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsActivity.java
new file mode 100644
index 0000000..c35d150
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/*
+ * A wrapper class for details activity
+ */
+public class DetailsActivity extends Activity
+{
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.details);
+
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsDescriptionPresenter.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsDescriptionPresenter.java
new file mode 100644
index 0000000..39a83b9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsDescriptionPresenter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+
+public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
+
+    @Override
+    protected void onBindDescription(ViewHolder viewHolder, Object item) {
+        Movie movie = (Movie) item;
+
+        if (movie != null) {
+            viewHolder.getTitle().setText(movie.getTitle());
+            viewHolder.getSubtitle().setText(movie.getStudio());
+            viewHolder.getBody().setText(movie.getDescription());
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/LeanbackDetailsFragment.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/LeanbackDetailsFragment.java
new file mode 100644
index 0000000..21c3a70
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/LeanbackDetailsFragment.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.DetailsFragment;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.Row;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+
+/*
+ * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
+ * It shows a detailed view of video and its meta plus related videos.
+ */
+public class LeanbackDetailsFragment extends DetailsFragment {
+    private static final String TAG = "DetailsFragment";
+
+    private static final int ACTION_WATCH_TRAILER = 1;
+    private static final int ACTION_RENT = 2;
+    private static final int ACTION_BUY = 3;
+
+    private static final int DETAIL_THUMB_WIDTH = 274;
+    private static final int DETAIL_THUMB_HEIGHT = 274;
+
+    private Movie selectedMovie;
+
+    private Drawable mDefaultBackground;
+    private Target mBackgroundTarget;
+    private DisplayMetrics mMetrics;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate DetailsFragment");
+        super.onCreate(savedInstanceState);
+
+        BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
+        backgroundManager.attach(getActivity().getWindow());
+        mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
+
+        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+
+        selectedMovie = (Movie) getActivity().getIntent().getSerializableExtra("Movie");
+        Log.d(TAG, "DetailsActivity movie: " + selectedMovie.toString());
+        new DetailRowBuilderTask().execute(selectedMovie);
+
+        setOnItemClickedListener(getDefaultItemClickedListener());
+
+    }
+
+    private class DetailRowBuilderTask extends AsyncTask<Movie, Integer, DetailsOverviewRow> {
+        @Override
+        protected DetailsOverviewRow doInBackground(Movie... movies) {
+            selectedMovie = movies[0];
+
+            Log.d(TAG, "doInBackground: " + selectedMovie.toString());
+            DetailsOverviewRow row = new DetailsOverviewRow(selectedMovie);
+            try {
+                Bitmap poster = Picasso.with(getActivity())
+                        .load(selectedMovie.getCardImageUrl())
+                        .resize(Utils.dpToPx(DETAIL_THUMB_WIDTH, getActivity()
+                                .getApplicationContext()),
+                                Utils.dpToPx(DETAIL_THUMB_HEIGHT, getActivity()
+                                        .getApplicationContext()))
+                        .centerCrop()
+                        .get();
+                row.setImageBitmap(getActivity(), poster);
+                updateBackground(selectedMovie.getBackgroundImageURI());
+            } catch (IOException e) {
+            }
+
+            row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
+                    R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
+            row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
+                    getResources().getString(R.string.rent_2)));
+            row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
+                    getResources().getString(R.string.buy_2)));
+            return row;
+        }
+
+        @Override
+        protected void onPostExecute(DetailsOverviewRow detailRow) {
+            ClassPresenterSelector ps = new ClassPresenterSelector();
+            DetailsOverviewRowPresenter dorPresenter =
+                    new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+            // set detail background and style
+            dorPresenter.setBackgroundColor(getResources().getColor(R.color.detail_background));
+            dorPresenter.setStyleLarge(true);
+            dorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+                @Override
+                public void onActionClicked(Action action) {
+                    if (action.getId() == ACTION_WATCH_TRAILER) {
+                        Intent intent = new Intent(getActivity(), PlayerActivity.class);
+                        intent.putExtra(getResources().getString(R.string.movie), selectedMovie);
+                        intent.putExtra(getResources().getString(R.string.should_start), true);
+                        startActivity(intent);
+                    }
+                    else {
+                        Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+
+            ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
+            ps.addClassPresenter(ListRow.class,
+                    new ListRowPresenter());
+
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(ps);
+            adapter.add(detailRow);
+
+            String subcategories[] = {
+                    getString(R.string.related_movies)
+            };
+            HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+            for (HashMap.Entry<String, List<Movie>> entry : movies.entrySet())
+            {
+                if (selectedMovie.getCategory().indexOf(entry.getKey()) >= 0) {
+                    List<Movie> list = entry.getValue();
+                    for (int j = 0; j < list.size(); j++) {
+                        listRowAdapter.add(list.get(j));
+                    }
+                }
+            }
+            HeaderItem header = new HeaderItem(0, subcategories[0], null);
+            adapter.add(new ListRow(header, listRowAdapter));
+
+            setAdapter(adapter);
+        }
+
+    }
+
+    protected OnItemClickedListener getDefaultItemClickedListener() {
+        return new OnItemClickedListener() {
+            @Override
+            public void onItemClicked(Object item, Row row) {
+                if (item instanceof Movie) {
+                    Movie movie = (Movie) item;
+                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
+                    intent.putExtra(getResources().getString(R.string.movie), movie);
+                    startActivity(intent);
+                }
+            }
+        };
+    }
+
+    protected void updateBackground(URI uri) {
+        Picasso.with(getActivity())
+                .load(uri.toString())
+                .resize(mMetrics.widthPixels, mMetrics.heightPixels)
+                .error(mDefaultBackground)
+                .into(mBackgroundTarget);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainActivity.java
new file mode 100644
index 0000000..a63a3c9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/*
+ * A wrapper class for main view of the app
+ */
+public class MainActivity extends Activity {
+    /** Called when the activity is first created. */
+
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainFragment.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainFragment.java
new file mode 100644
index 0000000..5092165
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/MainFragment.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.LoaderManager;
+import android.content.Intent;
+import android.content.Loader;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/*
+ * Main class to show BrowseFragment with header and rows of videos
+ */
+public class MainFragment extends BrowseFragment implements
+        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
+    private static final String TAG = "MainFragment";
+
+    private static int BACKGROUND_UPDATE_DELAY = 300;
+    private static int GRID_ITEM_WIDTH = 200;
+    private static int GRID_ITEM_HEIGHT = 200;
+
+    private ArrayObjectAdapter mRowsAdapter;
+    private Drawable mDefaultBackground;
+    private Target mBackgroundTarget;
+    private DisplayMetrics mMetrics;
+    private Timer mBackgroundTimer;
+    private final Handler mHandler = new Handler();
+    private URI mBackgroundURI;
+    private static String mVideosUrl;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onActivityCreated(savedInstanceState);
+
+        loadVideoData();
+
+        prepareBackgroundManager();
+        setupUIElements();
+        setupEventListeners();
+    }
+
+    private void prepareBackgroundManager() {
+        BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
+        backgroundManager.attach(getActivity().getWindow());
+        mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
+        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+    }
+
+    private void setupUIElements() {
+        // setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
+        setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent over title
+        setHeadersState(HEADERS_ENABLED);
+        setHeadersTransitionOnBackEnabled(true);
+        // set fastLane (or headers) background color
+        setBrandColor(getResources().getColor(R.color.fastlane_background));
+        // set search icon color
+        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
+    }
+
+    private void loadVideoData() {
+        VideoProvider.setContext(getActivity());
+        mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    private void setupEventListeners() {
+        setOnSearchClickedListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent(getActivity(), SearchActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        setOnItemSelectedListener(getDefaultItemSelectedListener());
+        setOnItemClickedListener(getDefaultItemClickedListener());
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int,
+     * android.os.Bundle)
+     */
+    @Override
+    public Loader<HashMap<String, List<Movie>>> onCreateLoader(int arg0, Bundle arg1) {
+        Log.d(TAG, "VideoItemLoader created ");
+        return new VideoItemLoader(getActivity(), mVideosUrl);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android
+     * .support.v4.content.Loader, java.lang.Object)
+     */
+    @Override
+    public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
+            HashMap<String, List<Movie>> data) {
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        CardPresenter cardPresenter = new CardPresenter();
+
+        int i = 0;
+
+        for (HashMap.Entry<String, List<Movie>> entry : data.entrySet())
+        {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
+            List<Movie> list = entry.getValue();
+
+            for (int j = 0; j < list.size(); j++) {
+                listRowAdapter.add(list.get(j));
+            }
+            HeaderItem header = new HeaderItem(i, entry.getKey(), null);
+            i++;
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+
+        HeaderItem gridHeader = new HeaderItem(i, getResources().getString(R.string.preferences),
+                null);
+
+        GridItemPresenter gridPresenter = new GridItemPresenter();
+        ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
+        gridRowAdapter.add(getResources().getString(R.string.grid_view));
+        gridRowAdapter.add(getResources().getString(R.string.send_feeback));
+        gridRowAdapter.add(getResources().getString(R.string.personal_settings));
+        mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
+
+        setAdapter(mRowsAdapter);
+
+        updateRecommendations();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<HashMap<String, List<Movie>>> arg0) {
+        mRowsAdapter.clear();
+    }
+
+    protected OnItemSelectedListener getDefaultItemSelectedListener() {
+        return new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(Object item, Row row) {
+                if (item instanceof Movie) {
+                    mBackgroundURI = ((Movie) item).getBackgroundImageURI();
+                    startBackgroundTimer();
+                }
+            }
+        };
+    }
+
+    protected OnItemClickedListener getDefaultItemClickedListener() {
+        return new OnItemClickedListener() {
+            @Override
+            public void onItemClicked(Object item, Row row) {
+                if (item instanceof Movie) {
+                    Movie movie = (Movie) item;
+                    Log.d(TAG, "Item: " + item.toString());
+                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
+                    intent.putExtra(getString(R.string.movie), movie);
+                    startActivity(intent);
+                }
+                else if (item instanceof String) {
+                    if (((String) item).indexOf(getResources().getString(R.string.grid_view)) >= 0) {
+                        Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
+                        startActivity(intent);
+                    }
+                    else {
+                        Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
+                                .show();
+                    }
+                }
+
+            }
+        };
+    }
+
+    protected void setDefaultBackground(Drawable background) {
+        mDefaultBackground = background;
+    }
+
+    protected void setDefaultBackground(int resourceId) {
+        mDefaultBackground = getResources().getDrawable(resourceId);
+    }
+
+    protected void updateBackground(URI uri) {
+        Picasso.with(getActivity())
+                .load(uri.toString())
+                .resize(mMetrics.widthPixels, mMetrics.heightPixels)
+                .centerCrop()
+                .error(mDefaultBackground)
+                .into(mBackgroundTarget);
+    }
+
+    protected void updateBackground(Drawable drawable) {
+        BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
+    }
+
+    protected void clearBackground() {
+        BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
+    }
+
+    private void startBackgroundTimer() {
+        if (null != mBackgroundTimer) {
+            mBackgroundTimer.cancel();
+        }
+        mBackgroundTimer = new Timer();
+        mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
+    }
+
+    private class UpdateBackgroundTask extends TimerTask {
+
+        @Override
+        public void run() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mBackgroundURI != null) {
+                        updateBackground(mBackgroundURI);
+                    }
+                }
+            });
+        }
+    }
+
+    private class GridItemPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            TextView view = new TextView(parent.getContext());
+            view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
+            view.setFocusable(true);
+            view.setFocusableInTouchMode(true);
+            view.setBackgroundColor(getResources().getColor(R.color.default_background));
+            view.setTextColor(Color.WHITE);
+            view.setGravity(Gravity.CENTER);
+            return new ViewHolder(view);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+            ((TextView) viewHolder.view).setText((String) item);
+        }
+
+        @Override
+        public void onUnbindViewHolder(ViewHolder viewHolder) {
+        }
+    }
+
+    private void updateRecommendations() {
+        Intent recommendationIntent = new Intent(getActivity(), UpdateRecommendationsService.class);
+        getActivity().startService(recommendationIntent);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/Movie.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/Movie.java
new file mode 100644
index 0000000..adc01a9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/Movie.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.util.Log;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/*
+ * Movie class represents video entity with title, description, image thumbs and video url. 
+ * 
+ */
+public class Movie implements Serializable {
+    static final long serialVersionUID = 727566175075960653L;
+    private static long count = 0;
+    private long id;
+    private String title;
+    private String description;
+    private String bgImageUrl;
+    private String cardImageUrl;
+    private String videoUrl;
+    private String studio;
+    private String category;
+
+    public Movie() {
+    }
+
+    public static long getCount() {
+        return count;
+    }
+
+    public static void incCount() {
+        count++;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getStudio() {
+        return studio;
+    }
+
+    public void setStudio(String studio) {
+        this.studio = studio;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+
+    public String getBackgroundImageUrl() {
+        return bgImageUrl;
+    }
+
+    public void setBackgroundImageUrl(String bgImageUrl) {
+        this.bgImageUrl = bgImageUrl;
+    }
+
+    public String getCardImageUrl() {
+        return cardImageUrl;
+    }
+
+    public void setCardImageUrl(String cardImageUrl) {
+        this.cardImageUrl = cardImageUrl;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public void setCategory(String category) {
+        this.category = category;
+    }
+
+    public URI getBackgroundImageURI() {
+        try {
+            Log.d("BACK MOVIE: ", bgImageUrl);
+            return new URI(getBackgroundImageUrl());
+        } catch (URISyntaxException e) {
+            Log.d("URI exception: ", bgImageUrl);
+            return null;
+        }
+    }
+
+    public URI getCardImageURI() {
+        try {
+            return new URI(getCardImageUrl());
+        } catch (URISyntaxException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Movie{" +
+                "id=" + id +
+                ", title='" + title + '\'' +
+                ", videoUrl='" + videoUrl + '\'' +
+                ", backgroundImageUrl='" + bgImageUrl + '\'' +
+                ", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'' +
+                ", cardImageUrl='" + cardImageUrl + '\'' +
+                '}';
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/PicassoBackgroundManagerTarget.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/PicassoBackgroundManagerTarget.java
new file mode 100644
index 0000000..b8fa117
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/PicassoBackgroundManagerTarget.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.app.BackgroundManager;
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+/**
+ * Picasso target for updating default_background images
+ */
+public class PicassoBackgroundManagerTarget implements Target {
+    BackgroundManager mBackgroundManager;
+
+    public PicassoBackgroundManagerTarget(BackgroundManager backgroundManager) {
+        this.mBackgroundManager = backgroundManager;
+    }
+
+    @Override
+    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
+        this.mBackgroundManager.setBitmap(bitmap);
+    }
+
+    @Override
+    public void onBitmapFailed(Drawable drawable) {
+        this.mBackgroundManager.setDrawable(drawable);
+    }
+
+    @Override
+    public void onPrepareLoad(Drawable drawable) {
+        // Do nothing, default_background manager has its own transitions
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        PicassoBackgroundManagerTarget that = (PicassoBackgroundManagerTarget) o;
+
+        if (!mBackgroundManager.equals(that.mBackgroundManager))
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return mBackgroundManager.hashCode();
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/PlayerActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/PlayerActivity.java
new file mode 100644
index 0000000..d2faf6f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/PlayerActivity.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2013 Google Inc. All Rights Reserved.
+ *
+ * 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.example.android.leanback;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout.LayoutParams;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.VideoView;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.TimeUnit;
+
+/*
+ * PlayerActivity handles video playback 
+ */
+public class PlayerActivity extends Activity {
+
+    private static final String TAG = "PlayerActivity";
+
+    private static final int HIDE_CONTROLLER_TIME = 5000;
+    private static final int SEEKBAR_DELAY_TIME = 100;
+    private static final int SEEKBAR_INTERVAL_TIME = 1000;
+    private static final int MIN_SCRUB_TIME = 3000;
+    private static final int SCRUB_SEGMENT_DIVISOR = 30;
+    private static final double MEDIA_BAR_TOP_MARGIN = 0.8;
+    private static final double MEDIA_BAR_RIGHT_MARGIN = 0.2;
+    private static final double MEDIA_BAR_BOTTOM_MARGIN = 0.0;
+    private static final double MEDIA_BAR_LEFT_MARGIN = 0.2;
+    private static final double MEDIA_BAR_HEIGHT = 0.1;
+    private static final double MEDIA_BAR_WIDTH = 0.9;
+
+    private VideoView mVideoView;
+    private TextView mStartText;
+    private TextView mEndText;
+    private SeekBar mSeekbar;
+    private ImageView mPlayPause;
+    private ProgressBar mLoading;
+    private View mControllers;
+    private View mContainer;
+    private Timer mSeekbarTimer;
+    private Timer mControllersTimer;
+    private PlaybackState mPlaybackState;
+    private final Handler mHandler = new Handler();
+    private Movie mSelectedMovie;
+    private boolean mShouldStartPlayback;
+    private boolean mControllersVisible;
+    private int mDuration;
+    private DisplayMetrics mMetrics;
+
+    /*
+     * List of various states that we can be in
+     */
+    public static enum PlaybackState {
+        PLAYING, PAUSED, BUFFERING, IDLE;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.player_activity);
+
+        mMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+
+        loadViews();
+        setupController();
+        setupControlsCallbacks();
+        startVideoPlayer();
+        updateMetadata(true);
+    }
+
+    private void startVideoPlayer() {
+        Bundle b = getIntent().getExtras();
+        mSelectedMovie = (Movie) getIntent().getSerializableExtra(
+                getResources().getString(R.string.movie));
+        if (null != b) {
+            mShouldStartPlayback = b.getBoolean(getResources().getString(R.string.should_start));
+            int startPosition = b.getInt(getResources().getString(R.string.start_position), 0);
+            mVideoView.setVideoPath(mSelectedMovie.getVideoUrl());
+            if (mShouldStartPlayback) {
+                mPlaybackState = PlaybackState.PLAYING;
+                updatePlayButton(mPlaybackState);
+                if (startPosition > 0) {
+                    mVideoView.seekTo(startPosition);
+                }
+                mVideoView.start();
+                mPlayPause.requestFocus();
+                startControllersTimer();
+            } else {
+                updatePlaybackLocation();
+                mPlaybackState = PlaybackState.PAUSED;
+                updatePlayButton(mPlaybackState);
+            }
+        }
+    }
+
+    private void updatePlaybackLocation() {
+        if (mPlaybackState == PlaybackState.PLAYING ||
+                mPlaybackState == PlaybackState.BUFFERING) {
+            startControllersTimer();
+        } else {
+            stopControllersTimer();
+        }
+    }
+
+    private void play(int position) {
+        startControllersTimer();
+        mVideoView.seekTo(position);
+        mVideoView.start();
+        restartSeekBarTimer();
+    }
+
+    private void stopSeekBarTimer() {
+        if (null != mSeekbarTimer) {
+            mSeekbarTimer.cancel();
+        }
+    }
+
+    private void restartSeekBarTimer() {
+        stopSeekBarTimer();
+        mSeekbarTimer = new Timer();
+        mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), SEEKBAR_DELAY_TIME,
+                SEEKBAR_INTERVAL_TIME);
+    }
+
+    private void stopControllersTimer() {
+        if (null != mControllersTimer) {
+            mControllersTimer.cancel();
+        }
+    }
+
+    private void startControllersTimer() {
+        if (null != mControllersTimer) {
+            mControllersTimer.cancel();
+        }
+        mControllersTimer = new Timer();
+        mControllersTimer.schedule(new HideControllersTask(), HIDE_CONTROLLER_TIME);
+    }
+
+    private void updateControllersVisibility(boolean show) {
+        if (show) {
+            mControllers.setVisibility(View.VISIBLE);
+        } else {
+            mControllers.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Log.d(TAG, "onPause() was called");
+        if (null != mSeekbarTimer) {
+            mSeekbarTimer.cancel();
+            mSeekbarTimer = null;
+        }
+        if (null != mControllersTimer) {
+            mControllersTimer.cancel();
+        }
+        mVideoView.pause();
+        mPlaybackState = PlaybackState.PAUSED;
+        updatePlayButton(PlaybackState.PAUSED);
+    }
+
+    @Override
+    protected void onStop() {
+        Log.d(TAG, "onStop() was called");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "onDestroy() is called");
+        stopControllersTimer();
+        stopSeekBarTimer();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStart() {
+        Log.d(TAG, "onStart() was called");
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.d(TAG, "onResume() was called");
+        super.onResume();
+    }
+
+    private class HideControllersTask extends TimerTask {
+        @Override
+        public void run() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    updateControllersVisibility(false);
+                    mControllersVisible = false;
+                }
+            });
+
+        }
+    }
+
+    private class UpdateSeekbarTask extends TimerTask {
+
+        @Override
+        public void run() {
+            mHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    int currentPos = 0;
+                    currentPos = mVideoView.getCurrentPosition();
+                    updateSeekbar(currentPos, mDuration);
+                }
+            });
+        }
+    }
+
+    private class BackToDetailTask extends TimerTask {
+
+        @Override
+        public void run() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    Intent intent = new Intent(PlayerActivity.this, DetailsActivity.class);
+                    intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
+                    startActivity(intent);
+                }
+            });
+
+        }
+    }
+
+    private void setupController() {
+
+        int w = (int) (mMetrics.widthPixels * MEDIA_BAR_WIDTH);
+        int h = (int) (mMetrics.heightPixels * MEDIA_BAR_HEIGHT);
+        int marginLeft = (int) (mMetrics.widthPixels * MEDIA_BAR_LEFT_MARGIN);
+        int marginTop = (int) (mMetrics.heightPixels * MEDIA_BAR_TOP_MARGIN);
+        int marginRight = (int) (mMetrics.widthPixels * MEDIA_BAR_RIGHT_MARGIN);
+        int marginBottom = (int) (mMetrics.heightPixels * MEDIA_BAR_BOTTOM_MARGIN);
+        LayoutParams lp = new LayoutParams(w, h);
+        lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
+        mControllers.setLayoutParams(lp);
+        mStartText.setText(getResources().getString(R.string.init_text));
+        mEndText.setText(getResources().getString(R.string.init_text));
+    }
+
+    private void setupControlsCallbacks() {
+
+        mVideoView.setOnErrorListener(new OnErrorListener() {
+
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                String msg = "";
+                if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
+                    msg = getString(R.string.video_error_media_load_timeout);
+                } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+                    msg = getString(R.string.video_error_server_unaccessible);
+                } else {
+                    msg = getString(R.string.video_error_unknown_error);
+                }
+                Utils.showErrorDialog(PlayerActivity.this, msg);
+                mVideoView.stopPlayback();
+                mPlaybackState = PlaybackState.IDLE;
+                return false;
+            }
+        });
+
+        mVideoView.setOnPreparedListener(new OnPreparedListener() {
+
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.d(TAG, "onPrepared is reached");
+                mDuration = mp.getDuration();
+                mEndText.setText(formatTimeSignature(mDuration));
+                mSeekbar.setMax(mDuration);
+                restartSeekBarTimer();
+            }
+        });
+
+        mVideoView.setOnCompletionListener(new OnCompletionListener() {
+
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                stopSeekBarTimer();
+                mPlaybackState = PlaybackState.IDLE;
+                updatePlayButton(PlaybackState.IDLE);
+                mControllersTimer = new Timer();
+                mControllersTimer.schedule(new BackToDetailTask(), HIDE_CONTROLLER_TIME);
+            }
+        });
+    }
+
+    /*
+     * @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return
+     * super.onKeyDown(keyCode, event); }
+     */
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        int currentPos = 0;
+        int delta = (int) (mDuration / SCRUB_SEGMENT_DIVISOR);
+        if (delta < MIN_SCRUB_TIME)
+            delta = MIN_SCRUB_TIME;
+
+        Log.v("keycode", "duration " + mDuration + " delta:" + delta);
+        if (!mControllersVisible) {
+            updateControllersVisibility(true);
+        }
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                return true;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                return true;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                currentPos = mVideoView.getCurrentPosition();
+                currentPos -= delta;
+                if (currentPos > 0)
+                    play(currentPos);
+                return true;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                currentPos = mVideoView.getCurrentPosition();
+                currentPos += delta;
+                if (currentPos < mDuration)
+                    play(currentPos);
+                return true;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void updateSeekbar(int position, int duration) {
+        mSeekbar.setProgress(position);
+        mSeekbar.setMax(duration);
+        mStartText.setText(formatTimeSignature(mDuration));
+    }
+
+    private void updatePlayButton(PlaybackState state) {
+        switch (state) {
+            case PLAYING:
+                mLoading.setVisibility(View.INVISIBLE);
+                mPlayPause.setVisibility(View.VISIBLE);
+                mPlayPause.setImageDrawable(
+                        getResources().getDrawable(R.drawable.ic_pause_playcontrol_normal));
+                break;
+            case PAUSED:
+            case IDLE:
+                mLoading.setVisibility(View.INVISIBLE);
+                mPlayPause.setVisibility(View.VISIBLE);
+                mPlayPause.setImageDrawable(
+                        getResources().getDrawable(R.drawable.ic_play_playcontrol_normal));
+                break;
+            case BUFFERING:
+                mPlayPause.setVisibility(View.INVISIBLE);
+                mLoading.setVisibility(View.VISIBLE);
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void updateMetadata(boolean visible) {
+        mVideoView.invalidate();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return true;
+    }
+
+    private void loadViews() {
+        mVideoView = (VideoView) findViewById(R.id.videoView);
+        mStartText = (TextView) findViewById(R.id.startText);
+        mEndText = (TextView) findViewById(R.id.endText);
+        mSeekbar = (SeekBar) findViewById(R.id.seekBar);
+        mPlayPause = (ImageView) findViewById(R.id.playpause);
+        mLoading = (ProgressBar) findViewById(R.id.progressBar);
+        mControllers = findViewById(R.id.controllers);
+        mContainer = findViewById(R.id.container);
+
+        mVideoView.setOnClickListener(mPlayPauseHandler);
+    }
+
+    View.OnClickListener mPlayPauseHandler = new View.OnClickListener() {
+        public void onClick(View v) {
+            Log.d(TAG, "clicked play pause button");
+
+            if (!mControllersVisible) {
+                updateControllersVisibility(true);
+            }
+
+            if (mPlaybackState == PlaybackState.PAUSED) {
+                mPlaybackState = PlaybackState.PLAYING;
+                updatePlayButton(mPlaybackState);
+                mVideoView.start();
+                startControllersTimer();
+            } else {
+                mVideoView.pause();
+                mPlaybackState = PlaybackState.PAUSED;
+                updatePlayButton(PlaybackState.PAUSED);
+                stopControllersTimer();
+            }
+        }
+    };
+
+    private String formatTimeSignature(int timeSignature) {
+        return String.format(Locale.US,
+                "%02d:%02d",
+                TimeUnit.MILLISECONDS.toMinutes(timeSignature),
+                TimeUnit.MILLISECONDS.toSeconds(timeSignature)
+                        -
+                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS
+                                .toMinutes(timeSignature)));
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/RecommendationBuilder.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/RecommendationBuilder.java
new file mode 100644
index 0000000..7ef96bc
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/RecommendationBuilder.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import com.squareup.picasso.Picasso;
+
+import java.io.IOException;
+
+/*
+ * This class builds recommendations as notifications with videos as inputs.
+ */
+public class RecommendationBuilder {
+    private static final String TAG = "RecommendationBuilder";
+
+    private static int CARD_WIDTH = 313;
+    private static int CARD_HEIGHT = 176;
+
+    public static final String EXTRA_BACKGROUND_IMAGE_URL = "background_image_url";
+    private Context mContext;
+    private NotificationManager mNotificationManager;
+
+    private int mId;
+    private int mPriority;
+    private int mSmallIcon;
+    private String mTitle;
+    private String mDescription;
+    private String mImageUri;
+    private String mBackgroundUri;
+    private PendingIntent mIntent;
+
+    public RecommendationBuilder() {
+    }
+
+    public RecommendationBuilder setContext(Context context) {
+        mContext = context;
+        return this;
+    }
+
+    public RecommendationBuilder setId(int id) {
+        mId = id;
+        return this;
+    }
+
+    public RecommendationBuilder setPriority(int priority) {
+        mPriority = priority;
+        return this;
+    }
+
+    public RecommendationBuilder setTitle(String title) {
+        mTitle = title;
+        return this;
+    }
+
+    public RecommendationBuilder setDescription(String description) {
+        mDescription = description;
+        return this;
+    }
+
+    public RecommendationBuilder setImage(String uri) {
+        mImageUri = uri;
+        return this;
+    }
+
+    public RecommendationBuilder setBackground(String uri) {
+        mBackgroundUri = uri;
+        return this;
+    }
+
+    public RecommendationBuilder setIntent(PendingIntent intent) {
+        mIntent = intent;
+        return this;
+    }
+
+    public RecommendationBuilder setSmallIcon(int resourceId) {
+        mSmallIcon = resourceId;
+        return this;
+    }
+
+    public Notification build() throws IOException {
+
+        Log.d(TAG, "Building notification - " + this.toString());
+
+        if (mNotificationManager == null) {
+            mNotificationManager = (NotificationManager) mContext
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        Bundle extras = new Bundle();
+        if (mBackgroundUri != null) {
+            extras.putString(EXTRA_BACKGROUND_IMAGE_URL, mBackgroundUri);
+        }
+
+        Bitmap image = Picasso.with(mContext)
+                .load(mImageUri)
+                .resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
+                .get();
+
+        Notification notification = new NotificationCompat.BigPictureStyle(
+                new NotificationCompat.Builder(mContext)
+                        .setContentTitle(mTitle)
+                        .setContentText(mDescription)
+                        .setPriority(mPriority)
+                        .setLocalOnly(true)
+                        .setOngoing(true)
+                        .setColor(mContext.getResources().getColor(R.color.fastlane_background))
+                        // .setCategory(Notification.CATEGORY_RECOMMENDATION)
+                        .setCategory("recommendation")
+                        .setLargeIcon(image)
+                        .setSmallIcon(mSmallIcon)
+                        .setContentIntent(mIntent)
+                        .setExtras(extras))
+                .build();
+
+        mNotificationManager.notify(mId, notification);
+        mNotificationManager = null;
+        return notification;
+    }
+
+    @Override
+    public String toString() {
+        return "RecommendationBuilder{" +
+                ", mId=" + mId +
+                ", mPriority=" + mPriority +
+                ", mSmallIcon=" + mSmallIcon +
+                ", mTitle='" + mTitle + '\'' +
+                ", mDescription='" + mDescription + '\'' +
+                ", mImageUri='" + mImageUri + '\'' +
+                ", mBackgroundUri='" + mBackgroundUri + '\'' +
+                ", mIntent=" + mIntent +
+                '}';
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchActivity.java
new file mode 100644
index 0000000..0ce0fb9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/*
+ * This class is a wrapper activity for SearchFragment
+ */
+public class SearchActivity extends Activity
+{
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.search);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchFragment.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchFragment.java
new file mode 100644
index 0000000..944309a
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchFragment.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.Row;
+import android.text.TextUtils;
+import android.util.Log;
+
+/*
+ * This class demonstrates how to do in-app search 
+ */
+@SuppressLint("DefaultLocale")
+public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
+        implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
+    private static final String TAG = "SearchFragment";
+    private static final int SEARCH_DELAY_MS = 300;
+
+    private ArrayObjectAdapter mRowsAdapter;
+    private Handler mHandler = new Handler();
+    private SearchRunnable mDelayedLoad;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        setSearchResultProvider(this);
+        setOnItemClickedListener(getDefaultItemClickedListener());
+        mDelayedLoad = new SearchRunnable();
+    }
+
+    @Override
+    public ObjectAdapter getResultsAdapter() {
+        return mRowsAdapter;
+    }
+
+    private void queryByWords(String words) {
+        mRowsAdapter.clear();
+        if (!TextUtils.isEmpty(words)) {
+            mDelayedLoad.setSearchQuery(words);
+            mHandler.removeCallbacks(mDelayedLoad);
+            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+        }
+    }
+
+    @Override
+    public boolean onQueryTextChange(String newQuery) {
+        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
+        queryByWords(newQuery);
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        Log.i(TAG, String.format("Search Query Text Submit %s", query));
+        queryByWords(query);
+        return true;
+    }
+
+    private void loadRows(String query) {
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+        for (HashMap.Entry<String, List<Movie>> entry : movies.entrySet())
+        {
+            for (int i = 0; i < entry.getValue().size(); i++) {
+                Movie movie = entry.getValue().get(i);
+                if (movie.getTitle().toLowerCase(Locale.ENGLISH)
+                        .indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0
+                        || movie.getDescription().toLowerCase(Locale.ENGLISH)
+                                .indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0) {
+                    listRowAdapter.add(movie);
+                }
+            }
+        }
+        HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results),
+                null);
+        mRowsAdapter.add(new ListRow(header, listRowAdapter));
+    }
+
+    protected OnItemClickedListener getDefaultItemClickedListener() {
+        return new OnItemClickedListener() {
+            @Override
+            public void onItemClicked(Object item, Row row) {
+                if (item instanceof Movie) {
+                    Movie movie = (Movie) item;
+                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
+                    intent.putExtra(getResources().getString(R.string.movie), movie);
+                    startActivity(intent);
+                }
+            }
+        };
+    }
+
+    private class SearchRunnable implements Runnable {
+
+        private volatile String searchQuery;
+
+        public SearchRunnable() {
+        }
+
+        public void run() {
+            loadRows(searchQuery);
+        }
+
+        public void setSearchQuery(String value) {
+            this.searchQuery = value;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/UpdateRecommendationsService.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/UpdateRecommendationsService.java
new file mode 100644
index 0000000..f0695e8
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/UpdateRecommendationsService.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+
+/*
+ * This class builds up to MAX_RECOMMMENDATIONS of recommendations and defines what happens 
+ * when they're clicked from Recommendations seciton on Home screen
+ */
+public class UpdateRecommendationsService extends IntentService {
+    private static final String TAG = "UpdateRecommendationsService";
+    private static final int MAX_RECOMMENDATIONS = 3;
+
+    public UpdateRecommendationsService() {
+        super("RecommendationService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        Log.d(TAG, "Updating recommendation cards");
+        HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
+
+        int count = 0;
+
+        try {
+            RecommendationBuilder builder = new RecommendationBuilder()
+                    .setContext(getApplicationContext())
+                    .setSmallIcon(R.drawable.videos_by_google_icon);
+
+            for (HashMap.Entry<String, List<Movie>> entry : recommendations.entrySet())
+            {
+                for (int i = 0; i < entry.getValue().size(); i++) {
+                    Movie movie = entry.getValue().get(i);
+                    Log.d(TAG, "Recommendation - " + movie.getTitle());
+
+                    builder.setBackground(movie.getCardImageUrl())
+                            .setId(count + 1)
+                            .setPriority(MAX_RECOMMENDATIONS - count)
+                            .setTitle(movie.getTitle())
+                            .setDescription(getString(R.string.popular_header))
+                            .setImage(movie.getCardImageUrl())
+                            .setIntent(buildPendingIntent(movie))
+                            .build();
+
+                    if (++count >= MAX_RECOMMENDATIONS) {
+                        break;
+                    }
+                }
+                if (++count >= MAX_RECOMMENDATIONS) {
+                    break;
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to update recommendation", e);
+        }
+    }
+
+    private PendingIntent buildPendingIntent(Movie movie) {
+        Intent detailsIntent = new Intent(this, DetailsActivity.class);
+        detailsIntent.putExtra("Movie", movie);
+
+        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+        stackBuilder.addParentStack(DetailsActivity.class);
+        stackBuilder.addNextIntent(detailsIntent);
+        // Ensure a unique PendingIntents, otherwise all recommendations end up with the same
+        // PendingIntent
+        detailsIntent.setAction(Long.toString(movie.getId()));
+
+        PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+        return intent;
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/Utils.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/Utils.java
new file mode 100644
index 0000000..7f66018
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/Utils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013 Google Inc. All Rights Reserved.
+ *
+ * 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.example.android.leanback;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Point;
+import android.view.Display;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+/**
+ * A collection of utility methods, all static.
+ */
+public class Utils {
+
+    /*
+     * Making sure public utility methods remain static
+     */
+    private Utils() {
+    }
+
+    /**
+     * Returns the screen/display size
+     * 
+     * @param context
+     * @return
+     */
+    public static Point getDisplaySize(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+        int width = size.x;
+        int height = size.y;
+        return new Point(width, height);
+    }
+
+    /**
+     * Shows an error dialog with a given text message.
+     * 
+     * @param context
+     * @param errorString
+     */
+
+    public static final void showErrorDialog(Context context, String errorString) {
+        new AlertDialog.Builder(context).setTitle(R.string.error)
+                .setMessage(errorString)
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int id) {
+                        dialog.cancel();
+                    }
+                })
+                .create()
+                .show();
+    }
+
+    /**
+     * Shows a (long) toast
+     * 
+     * @param context
+     * @param msg
+     */
+    public static void showToast(Context context, String msg) {
+        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
+    }
+
+    /**
+     * Shows a (long) toast.
+     * 
+     * @param context
+     * @param resourceId
+     */
+    public static void showToast(Context context, int resourceId) {
+        Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
+    }
+
+    public static int dpToPx(int dp, Context ctx) {
+        float density = ctx.getResources().getDisplayMetrics().density;
+        return Math.round((float) dp * density);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridActivity.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridActivity.java
new file mode 100644
index 0000000..05e490d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/*
+ * Wrapper class for VerticalGridFragment
+ */
+public class VerticalGridActivity extends Activity
+{
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.vertical_grid);
+        getWindow().setBackgroundDrawableResource(R.drawable.grid_bg);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridFragment.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridFragment.java
new file mode 100644
index 0000000..5273700
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridFragment.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.example.android.leanback;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.util.Log;
+
+/*
+ * VerticalGridFragment shows a grid of videos
+ */
+public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
+    private static final String TAG = "VerticalGridFragment";
+
+    private static final int NUM_COLUMNS = 5;
+
+    private ArrayObjectAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setTitle(getString(R.string.vertical_grid_title));
+
+        setupFragment();
+    }
+
+    private void setupFragment() {
+        VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
+        gridPresenter.setNumberOfColumns(NUM_COLUMNS);
+        setGridPresenter(gridPresenter);
+
+        mAdapter = new ArrayObjectAdapter(new CardPresenter());
+
+        long seed = System.nanoTime();
+
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+
+        for (HashMap.Entry<String, List<Movie>> entry : movies.entrySet())
+        {
+            List<Movie> list = entry.getValue();
+            Collections.shuffle(list, new Random(seed));
+            for (int j = 0; j < list.size(); j++) {
+                mAdapter.add(list.get(j));
+            }
+        }
+
+        setAdapter(mAdapter);
+
+        setOnItemSelectedListener(new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(Object item, Row row) {
+            }
+        });
+
+        setOnItemClickedListener(new OnItemClickedListener() {
+            @Override
+            public void onItemClicked(Object item, Row row) {
+                if (item instanceof Movie) {
+                    Movie movie = (Movie) item;
+                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
+                    intent.putExtra(getString(R.string.movie), movie);
+                    startActivity(intent);
+                }
+            }
+        });
+
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoItemLoader.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoItemLoader.java
new file mode 100644
index 0000000..108d179
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoItemLoader.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 Google Inc. All Rights Reserved. 
+ *
+ * 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.example.android.leanback;
+
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.util.Log;
+
+/*
+ * This class asynchronously loads videos from a backend
+ */
+public class VideoItemLoader extends AsyncTaskLoader<HashMap<String, List<Movie>>> {
+
+    private static final String TAG = "VideoItemLoader";
+    private final String mUrl;
+    private Context mContext;
+
+    public VideoItemLoader(Context context, String url) {
+        super(context);
+        mContext = context;
+        mUrl = url;
+    }
+
+    @Override
+    public HashMap<String, List<Movie>> loadInBackground() {
+        try {
+            return VideoProvider.buildMedia(mContext, mUrl);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to fetch media data", e);
+            return null;
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        forceLoad();
+    }
+
+    /**
+     * Handles a request to stop the Loader.
+     */
+    @Override
+    protected void onStopLoading() {
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoProvider.java b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoProvider.java
new file mode 100644
index 0000000..cd7ebfd
--- /dev/null
+++ b/prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoProvider.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 Google Inc. All Rights Reserved.
+ *
+ * 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.example.android.leanback;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/*
+ * This class loads videos from a backend and saves them into a HashMap
+ */
+public class VideoProvider {
+
+    private static final String TAG = "VideoProvider";
+    private static String TAG_MEDIA = "videos";
+    private static String TAG_GOOGLE_VIDEOS = "googlevideos";
+    private static String TAG_CATEGORY = "category";
+    private static String TAG_STUDIO = "studio";
+    private static String TAG_SOURCES = "sources";
+    private static String TAG_DESCRIPTION = "description";
+    private static String TAG_CARD_THUMB = "card";
+    private static String TAG_BACKGROUND = "background";
+    private static String TAG_TITLE = "title";
+
+    private static HashMap<String, List<Movie>> mMovieList;
+    private static Context mContext;
+    private static String mPrefixUrl;
+
+    public static void setContext(Context context) {
+        if (mContext == null)
+            mContext = context;
+    }
+
+    protected JSONObject parseUrl(String urlString) {
+        Log.d(TAG, "Parse URL: " + urlString);
+        InputStream is = null;
+
+        mPrefixUrl = mContext.getResources().getString(R.string.prefix_url);
+
+        try {
+            java.net.URL url = new java.net.URL(urlString);
+            URLConnection urlConnection = url.openConnection();
+            is = new BufferedInputStream(urlConnection.getInputStream());
+            BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    urlConnection.getInputStream(), "iso-8859-1"), 8);
+            StringBuilder sb = new StringBuilder();
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String json = sb.toString();
+            return new JSONObject(json);
+        } catch (Exception e) {
+            Log.d(TAG, "Failed to parse the json for media list", e);
+            return null;
+        } finally {
+            if (null != is) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.d(TAG, "JSON feed closed", e);
+                }
+            }
+        }
+    }
+
+    public static HashMap<String, List<Movie>> getMovieList() {
+        return mMovieList;
+    }
+
+    public static HashMap<String, List<Movie>> buildMedia(Context ctx, String url)
+            throws JSONException {
+        if (null != mMovieList) {
+            return mMovieList;
+        }
+        mMovieList = new HashMap<String, List<Movie>>();
+
+        JSONObject jsonObj = new VideoProvider().parseUrl(url);
+        JSONArray categories = jsonObj.getJSONArray(TAG_GOOGLE_VIDEOS);
+        if (null != categories) {
+            Log.d(TAG, "category #: " + categories.length());
+            String title = new String();
+            String videoUrl = new String();
+            String bgImageUrl = new String();
+            String cardImageUrl = new String();
+            String studio = new String();
+            for (int i = 0; i < categories.length(); i++) {
+                JSONObject category = categories.getJSONObject(i);
+                String category_name = category.getString(TAG_CATEGORY);
+                JSONArray videos = category.getJSONArray(TAG_MEDIA);
+                Log.d(TAG,
+                        "category: " + i + " Name:" + category_name + " video length: "
+                                + videos.length());
+                List<Movie> categoryList = new ArrayList<Movie>();
+                if (null != videos) {
+                    for (int j = 0; j < videos.length(); j++) {
+                        JSONObject video = videos.getJSONObject(j);
+                        String description = video.getString(TAG_DESCRIPTION);
+                        JSONArray videoUrls = video.getJSONArray(TAG_SOURCES);
+                        if (null == videoUrls || videoUrls.length() == 0) {
+                            continue;
+                        }
+                        title = video.getString(TAG_TITLE);
+                        videoUrl = getVideoPrefix(category_name, videoUrls.getString(0));
+                        bgImageUrl = getThumbPrefix(category_name, title,
+                                video.getString(TAG_BACKGROUND));
+                        cardImageUrl = getThumbPrefix(category_name, title,
+                                video.getString(TAG_CARD_THUMB));
+                        studio = video.getString(TAG_STUDIO);
+                        categoryList.add(buildMovieInfo(category_name, title, description, studio,
+                                videoUrl, cardImageUrl,
+                                bgImageUrl));
+                    }
+                    mMovieList.put(category_name, categoryList);
+                }
+            }
+        }
+        return mMovieList;
+    }
+
+    private static Movie buildMovieInfo(String category, String title,
+            String description, String studio, String videoUrl, String cardImageUrl,
+            String bgImageUrl) {
+        Movie movie = new Movie();
+        movie.setId(Movie.getCount());
+        Movie.incCount();
+        movie.setTitle(title);
+        movie.setDescription(description);
+        movie.setStudio(studio);
+        movie.setCategory(category);
+        movie.setCardImageUrl(cardImageUrl);
+        movie.setBackgroundImageUrl(bgImageUrl);
+        movie.setVideoUrl(videoUrl);
+
+        return movie;
+    }
+
+    private static String getVideoPrefix(String category, String videoUrl) {
+        String ret = "";
+        ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
+                videoUrl.replace(" ", "%20");
+        return ret;
+    }
+
+    private static String getThumbPrefix(String category, String title, String imageUrl) {
+        String ret = "";
+
+        ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
+                title.replace(" ", "%20") + '/' +
+                imageUrl.replace(" ", "%20");
+        return ret;
+    }
+}
diff --git a/prebuilts/androidtv/visual-game-controller/AndroidManifest.xml b/prebuilts/androidtv/visual-game-controller/AndroidManifest.xml
new file mode 100644
index 0000000..565264e
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2014 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.visualgamecontroller"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.visualgamecontroller.FullscreenActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:label="@string/app_name"
+            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 
+            android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/prebuilts/androidtv/visual-game-controller/CONTRIBUTING.md b/prebuilts/androidtv/visual-game-controller/CONTRIBUTING.md
new file mode 100644
index 0000000..59e2c2f
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we 
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement
+(CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (http://code.google.com/legal/individual-cla-v1.0.html).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (http://code.google.com/legal/corporate-cla-v1.0.html).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing a Patch
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your change to the repo in question.
+    * Fork the desired repo, develop and test your code changes.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+1. The repo owner will review your request. If it is approved, the change will
+   be merged. If it needs additional work, the repo owner will respond with
+   useful comments.
+
+## Contributing a New Sample App
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your own repo for your app following this naming convention:
+    * mirror-{app-name}-{language or plaform}
+    * apps: quickstart, photohunt-server, photohunt-client
+    * example:  mirror-quickstart-android
+    * For multi-language apps, concatenate the primary languages like this:
+      mirror-photohunt-server-java-python.
+
+1. Create your sample app in this repo.
+    * Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
+      googlecast repo.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+    * Instructional value is the top priority when evaluating new app proposals for
+      this collection of repos.
+1. Submit a request to fork your repo in googlecast organization.
+1. The repo owner will review your request. If it is approved, the sample will
+   be merged. If it needs additional work, the repo owner will respond with 
+   useful comments.
diff --git a/prebuilts/androidtv/visual-game-controller/LICENSE b/prebuilts/androidtv/visual-game-controller/LICENSE
new file mode 100644
index 0000000..8405e89
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/prebuilts/androidtv/visual-game-controller/README.md b/prebuilts/androidtv/visual-game-controller/README.md
new file mode 100644
index 0000000..d8ae351
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/README.md
@@ -0,0 +1,24 @@
+# VisualGameController
+
+The Visual Game Controller app is designed to run on an Android TV device and displays visual feedback for the buttons of an attached game controller.
+
+## Dependencies
+* Android SDK v7 appcompat library
+
+## Setup Instructions
+* Compile and deploy to your Android TV device.
+* If using gradle, make sure you update build.gradle and settings.gradle to reflect the name you gave your VisualGameController project when you cloned it.
+
+## References and How to report bugs
+* [Developer Documentation](http://developers.google.com/)
+
+## How to make contributions?
+Please read and follow the steps in the CONTRIBUTING.md
+
+## License
+See LICENSE
+
+## Google+
+Google Developers Page on Google+ [https://plus.google.com/+GoogleDevelopers/posts](https://plus.google.com/+GoogleDevelopers/posts)
+
+## Change List
diff --git a/prebuilts/androidtv/visual-game-controller/build.gradle b/prebuilts/androidtv/visual-game-controller/build.gradle
new file mode 100644
index 0000000..ee65cfe
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/build.gradle
@@ -0,0 +1,36 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:0.10.+'
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+apply plugin: 'android'
+
+android {
+    compileSdkVersion 19
+    buildToolsVersion "19"
+
+    defaultConfig {
+        minSdkVersion 10
+        targetSdkVersion 19
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+}
+
+dependencies {
+    compile 'com.android.support:appcompat-v7:19.0.1'
+}
diff --git a/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.jar b/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..5838598
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.properties b/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f4c8417
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Mar 07 22:03:28 PST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/prebuilts/androidtv/visual-game-controller/gradlew b/prebuilts/androidtv/visual-game-controller/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/androidtv/visual-game-controller/gradlew.bat b/prebuilts/androidtv/visual-game-controller/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/prebuilts/androidtv/visual-game-controller/ic_launcher-web.png b/prebuilts/androidtv/visual-game-controller/ic_launcher-web.png
new file mode 100644
index 0000000..a18cbb4
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/ic_launcher-web.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/proguard-project.txt b/prebuilts/androidtv/visual-game-controller/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/prebuilts/androidtv/visual-game-controller/project.properties b/prebuilts/androidtv/visual-game-controller/project.properties
new file mode 100644
index 0000000..a257388
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library.reference.1=../../../android-sdk-macosx/extras/android/support/v7/appcompat
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-hdpi/ic_launcher.png b/prebuilts/androidtv/visual-game-controller/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..613cac2
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-mdpi/ic_launcher.png b/prebuilts/androidtv/visual-game-controller/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..39b981f
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xhdpi/ic_launcher.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d4ccbb
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png
new file mode 100644
index 0000000..e22f1ef
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png
new file mode 100644
index 0000000..5df2ce8
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_bottom.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_bottom.png
new file mode 100644
index 0000000..4fec6bb
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_bottom.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_left.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_left.png
new file mode 100644
index 0000000..15d2b42
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_left.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_right.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_right.png
new file mode 100644
index 0000000..b067134
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_right.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_top.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_top.png
new file mode 100644
index 0000000..9c307a2
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_top.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller.png
new file mode 100644
index 0000000..50301ef
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_axis.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_axis.png
new file mode 100644
index 0000000..758854a
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_axis.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_original.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_original.png
new file mode 100644
index 0000000..ef3d290
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_original.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_paddles.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_paddles.png
new file mode 100644
index 0000000..977ac59
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_paddles.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png
new file mode 100644
index 0000000..b14e842
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6357c2c
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/led_blue.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/led_blue.png
new file mode 100644
index 0000000..0574d2d
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/led_blue.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/left_paddle.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/left_paddle.png
new file mode 100644
index 0000000..28ff638
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/left_paddle.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/right_paddle.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/right_paddle.png
new file mode 100644
index 0000000..692041f
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/right_paddle.png
Binary files differ
diff --git a/prebuilts/androidtv/visual-game-controller/res/layout/activity_fullscreen.xml b/prebuilts/androidtv/visual-game-controller/res/layout/activity_fullscreen.xml
new file mode 100644
index 0000000..ccc9a6c
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/layout/activity_fullscreen.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2014 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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:custom="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#000000"
+    tools:context="com.example.android.visualgamecontroller.FullscreenActivity" 
+    android:keepScreenOn="true">
+    
+    <LinearLayout
+        android:id="@+id/fullscreen_content"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+    <com.example.android.visualgamecontroller.ControllerView
+            android:id="@+id/controller"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:padding="10dp"
+            android:layout_weight="100"
+            custom:showText="true"
+            custom:labelHeight="20dp"
+            custom:labelWidth="110dp"
+            custom:labelY="85dp"
+            custom:labelPosition="left"
+            custom:highlightStrength="1.12"
+            android:background="@android:color/white"
+            custom:pieRotation="0"
+            custom:labelColor="@android:color/black"
+            custom:autoCenterPointerInSlice="true"
+            custom:pointerRadius="4dp"
+            />
+</LinearLayout>
+
+    <!--
+         This FrameLayout insets its children based on system windows using
+         android:fitsSystemWindows.
+    -->
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true" >
+
+        <LinearLayout
+            android:id="@+id/fullscreen_content_controls"
+            style="?metaButtonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:background="@color/black_overlay"
+            android:orientation="horizontal"
+            tools:ignore="UselessParent" >
+
+        </LinearLayout>
+    </FrameLayout>
+
+</FrameLayout>
diff --git a/prebuilts/androidtv/visual-game-controller/res/values-v11/styles.xml b/prebuilts/androidtv/visual-game-controller/res/values-v11/styles.xml
new file mode 100644
index 0000000..5c4ba5f
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values-v11/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="Theme.AppCompat.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+    <style name="FullscreenTheme" parent="android:Theme.Holo">
+        <item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
+        <item name="android:windowActionBarOverlay">true</item>
+        <item name="android:windowBackground">@null</item>
+        <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
+        <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
+    </style>
+
+    <style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
+        <item name="android:background">@color/black_overlay</item>
+    </style>
+
+</resources>
diff --git a/prebuilts/androidtv/visual-game-controller/res/values-v14/styles.xml b/prebuilts/androidtv/visual-game-controller/res/values-v14/styles.xml
new file mode 100644
index 0000000..664f4f1
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+<resources>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/prebuilts/androidtv/visual-game-controller/res/values/attrs.xml b/prebuilts/androidtv/visual-game-controller/res/values/attrs.xml
new file mode 100644
index 0000000..9f091eb
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values/attrs.xml
@@ -0,0 +1,37 @@
+<!-- Copyright (C) 2012 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>
+    <declare-styleable name="ButtonBarContainerTheme">
+        <attr name="metaButtonBarStyle" format="reference" />
+        <attr name="metaButtonBarButtonStyle" format="reference" />
+    </declare-styleable>
+    
+    <declare-styleable name="ControllerView">
+        <attr name="autoCenterPointerInSlice" format="boolean"/>
+        <attr name="highlightStrength" format="float"/>
+        <attr name="labelColor" format="color"/>
+        <attr name="labelHeight" format="dimension"/>
+        <attr name="labelPosition" format="enum">
+            <enum name="left" value="0"/>
+            <enum name="right" value="1"/>
+        </attr>
+        <attr name="labelWidth" format="dimension"/>
+        <attr name="labelY" format="dimension"/>
+        <attr name="pieRotation" format="integer"/>
+        <attr name="pointerRadius" format="dimension"/>
+        <attr name="showText" format="boolean"/>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/prebuilts/androidtv/visual-game-controller/res/values/colors.xml b/prebuilts/androidtv/visual-game-controller/res/values/colors.xml
new file mode 100644
index 0000000..d408330
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values/colors.xml
@@ -0,0 +1,13 @@
+<resources>
+
+    <color name="black_overlay">#66000000</color>
+    <color name="transparent_black">#cc000000</color>
+    
+    <color name="seafoam">#ffc6f9e5</color>
+    <color name="chartreuse">#ffb6e9b5</color>
+    <color name="emerald">#ffa6d9b5</color>
+    <color name="bluegrass">#ff96c9b5</color>
+    <color name="turquoise">#ff86b9b5</color>
+    <color name="slate">#ff76a9b5</color>
+
+</resources>
diff --git a/prebuilts/androidtv/visual-game-controller/res/values/strings.xml b/prebuilts/androidtv/visual-game-controller/res/values/strings.xml
new file mode 100644
index 0000000..01a28c6
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">VisualGameController</string>
+    <string name="message">Connect a game controller</string>
+
+</resources>
diff --git a/prebuilts/androidtv/visual-game-controller/res/values/styles.xml b/prebuilts/androidtv/visual-game-controller/res/values/styles.xml
new file mode 100644
index 0000000..b2cd8b0
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="Theme.AppCompat.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <style name="FullscreenTheme" parent="Theme.AppCompat.Light">
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowBackground">@null</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/ControllerView.java b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/ControllerView.java
new file mode 100644
index 0000000..f487b0e
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/ControllerView.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2014 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.example.android.visualgamecontroller;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+import com.example.android.visualgamecontroller.FullscreenActivity.AxesMapping;
+import com.example.android.visualgamecontroller.FullscreenActivity.ButtonMapping;
+
+/**
+ * Custom view to display the game controller state visually.
+ */
+public class ControllerView extends SurfaceView implements
+        SurfaceHolder.Callback {
+    private static final String TAG = "ControllerView";
+    private static final float IMAGE_RESOLUTION_HEIGHT = 1080.0F;
+    private static final int MAX_CONTROLLERS = 4;
+
+    private WindowManager mWindowManager;
+    private Bitmap mControllerBitmap;
+    private Bitmap mAxisBitmap;
+    private Bitmap mBlueLedBitmap;
+    private Bitmap mRightDirectionalBitmap;
+    private Bitmap mTopDirectionalBitmap;
+    private Bitmap mLeftDirectionalBitmap;
+    private Bitmap mBottomDirectionalBitmap;
+    private Bitmap mRightPaddleBitmap;
+    private Bitmap mLeftPaddleBitmap;
+    private Bitmap mGradientBitmap;
+    private Paint mBackgroundPaint;
+    private Paint mImagePaint;
+    private Paint mCirclePaint;
+    private Paint mLedPaint;
+    private Paint mDirectionalPaint;
+    private Paint mGradientPaint;
+    private Point mSize = new Point();
+    private float mDisplayRatio = 1.0f;
+    private int[] mButtons;
+    private float[] mAxes;
+    private int mCurrentControllerNumber = -1;
+    // Image asset locations
+    private float[] mYButton = {
+            823 / IMAGE_RESOLUTION_HEIGHT, 276 / IMAGE_RESOLUTION_HEIGHT,
+            34 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mXButton = {
+            744 / IMAGE_RESOLUTION_HEIGHT, 355 / IMAGE_RESOLUTION_HEIGHT,
+            34 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mBButton = {
+            903 / IMAGE_RESOLUTION_HEIGHT, 355 / IMAGE_RESOLUTION_HEIGHT,
+            34 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mAButton = {
+            823 / IMAGE_RESOLUTION_HEIGHT, 434 / IMAGE_RESOLUTION_HEIGHT,
+            34 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mPowerButton = {
+            533 / IMAGE_RESOLUTION_HEIGHT, 353 / IMAGE_RESOLUTION_HEIGHT,
+            50 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mHomeButton = {
+            624 / IMAGE_RESOLUTION_HEIGHT, 353 / IMAGE_RESOLUTION_HEIGHT,
+            30 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mBackButton = {
+            443 / IMAGE_RESOLUTION_HEIGHT, 353 / IMAGE_RESOLUTION_HEIGHT,
+            30 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLedButtons = {
+            463 / IMAGE_RESOLUTION_HEIGHT, 449 / IMAGE_RESOLUTION_HEIGHT,
+            502 / IMAGE_RESOLUTION_HEIGHT, 449 / IMAGE_RESOLUTION_HEIGHT,
+            539 / IMAGE_RESOLUTION_HEIGHT,
+            449 / IMAGE_RESOLUTION_HEIGHT, 574 / IMAGE_RESOLUTION_HEIGHT,
+            449 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightDirectionalButton = {
+            264 / IMAGE_RESOLUTION_HEIGHT, 336 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mTopDirectionalButton = {
+            218 / IMAGE_RESOLUTION_HEIGHT, 263 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftDirectionalButton = {
+            144 / IMAGE_RESOLUTION_HEIGHT, 337 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mBottomDirectionalButton = {
+            217 / IMAGE_RESOLUTION_HEIGHT, 384 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftAxis = {
+            305 / IMAGE_RESOLUTION_HEIGHT, 485 / IMAGE_RESOLUTION_HEIGHT,
+            63 / IMAGE_RESOLUTION_HEIGHT, 50 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightAxis = {
+            637 / IMAGE_RESOLUTION_HEIGHT, 485 / IMAGE_RESOLUTION_HEIGHT,
+            63 / IMAGE_RESOLUTION_HEIGHT, 50 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightPaddle = {
+            705 / IMAGE_RESOLUTION_HEIGHT, 166 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightPaddlePressed = {
+            705 / IMAGE_RESOLUTION_HEIGHT, 180 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftPaddle = {
+            135 / IMAGE_RESOLUTION_HEIGHT, 166 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftPaddlePressed = {
+            135 / IMAGE_RESOLUTION_HEIGHT, 180 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftAxisButton = {
+            368 / IMAGE_RESOLUTION_HEIGHT, 548 / IMAGE_RESOLUTION_HEIGHT,
+            64 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightAxisButton = {
+            700 / IMAGE_RESOLUTION_HEIGHT, 548 / IMAGE_RESOLUTION_HEIGHT,
+            64 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mRightGradient = {
+            705 / IMAGE_RESOLUTION_HEIGHT, 125 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float[] mLeftGradient = {
+            125 / IMAGE_RESOLUTION_HEIGHT, 125 / IMAGE_RESOLUTION_HEIGHT
+    };
+    private float mAxisLeftX, mAxisLeftY;
+    private float mAxisRightX, mAxisRightY;
+
+    /**
+     * Class constructor taking only a context. Use this constructor to create
+     * {@link ControllerView} objects from your own code.
+     * 
+     * @param context
+     */
+    public ControllerView(Context context) {
+        super(context);
+        init();
+    }
+
+    /**
+     * Class constructor taking a context and an attribute set. This constructor
+     * is used by the layout engine to construct a {@link ControllerView} from a
+     * set of XML attributes.
+     * 
+     * @param context
+     * @param attrs An attribute set which can contain attributes from
+     *            {@link com.example.android.customviews.R.styleable.ControllerView}
+     *            as well as attributes inherited from {@link android.view.View}
+     */
+    public ControllerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    /**
+     * Initialize the custom control.
+     */
+    private void init() {
+        mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        mBackgroundPaint = new Paint();
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
+        mBackgroundPaint.setDither(true);
+        mBackgroundPaint.setAntiAlias(true);
+
+        mImagePaint = new Paint();
+
+        mCirclePaint = new Paint();
+        mCirclePaint.setStyle(Paint.Style.FILL);
+        mCirclePaint.setDither(true);
+        mCirclePaint.setAntiAlias(true);
+
+        mLedPaint = new Paint();
+        mLedPaint.setStyle(Paint.Style.FILL);
+        mLedPaint.setDither(true);
+        mLedPaint.setAntiAlias(true);
+        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(20.0f, BlurMaskFilter.Blur.OUTER);
+        mLedPaint.setMaskFilter(blurMaskFilter);
+
+        mDirectionalPaint = new Paint();
+        mDirectionalPaint.setDither(true);
+        mDirectionalPaint.setAntiAlias(true);
+        mDirectionalPaint.setAlpha(204);
+
+        mGradientPaint = new Paint();
+        mGradientPaint.setDither(true);
+        mGradientPaint.setAntiAlias(true);
+        mGradientPaint.setAlpha(204);
+    }
+
+    private void loadBitmaps(int displayWidth, int displayHeight) {
+        // Load the image resources
+        mControllerBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.game_controller_paddles);
+        int controllerBitmapWidth = mControllerBitmap.getWidth();
+        int controllerBitmapHeight = mControllerBitmap.getHeight();
+        mAxisBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.axis);
+        mBlueLedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.led_blue);
+        mRightDirectionalBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.directional_right);
+        mTopDirectionalBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.directional_top);
+        mLeftDirectionalBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.directional_left);
+        mBottomDirectionalBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.directional_bottom);
+        mRightPaddleBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.right_paddle);
+        mLeftPaddleBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.left_paddle);
+        mGradientBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.gradient);
+
+        mControllerBitmap = Bitmap.createScaledBitmap(mControllerBitmap, displayHeight,
+                displayHeight, true);
+
+        mDisplayRatio = displayHeight * 1.0f / controllerBitmapHeight;
+        // Scale the image bitmaps
+        mAxisBitmap = Bitmap.createScaledBitmap(mAxisBitmap,
+                (int) (mAxisBitmap.getWidth() * mDisplayRatio),
+                (int) (mAxisBitmap.getHeight() * mDisplayRatio),
+                true);
+        mBlueLedBitmap = Bitmap.createScaledBitmap(mBlueLedBitmap,
+                (int) (mBlueLedBitmap.getWidth() * mDisplayRatio),
+                (int) (mBlueLedBitmap.getHeight() * mDisplayRatio),
+                true);
+        mRightDirectionalBitmap = Bitmap.createScaledBitmap(mRightDirectionalBitmap,
+                (int) (mRightDirectionalBitmap.getWidth() * mDisplayRatio),
+                (int) (mRightDirectionalBitmap.getHeight() * mDisplayRatio),
+                true);
+        mTopDirectionalBitmap = Bitmap.createScaledBitmap(mTopDirectionalBitmap,
+                (int) (mTopDirectionalBitmap.getWidth() * mDisplayRatio),
+                (int) (mTopDirectionalBitmap.getHeight() * mDisplayRatio),
+                true);
+        mLeftDirectionalBitmap = Bitmap.createScaledBitmap(mLeftDirectionalBitmap,
+                (int) (mLeftDirectionalBitmap.getWidth() * mDisplayRatio),
+                (int) (mLeftDirectionalBitmap.getHeight() * mDisplayRatio),
+                true);
+        mBottomDirectionalBitmap = Bitmap.createScaledBitmap(mBottomDirectionalBitmap,
+                (int) (mBottomDirectionalBitmap.getWidth() * mDisplayRatio),
+                (int) (mBottomDirectionalBitmap.getHeight() * mDisplayRatio),
+                true);
+        mRightPaddleBitmap = Bitmap.createScaledBitmap(mRightPaddleBitmap,
+                (int) (mRightPaddleBitmap.getWidth() * mDisplayRatio),
+                (int) (mRightPaddleBitmap.getHeight() * mDisplayRatio),
+                true);
+        mLeftPaddleBitmap = Bitmap.createScaledBitmap(mLeftPaddleBitmap,
+                (int) (mLeftPaddleBitmap.getWidth() * mDisplayRatio),
+                (int) (mLeftPaddleBitmap.getHeight() * mDisplayRatio),
+                true);
+        mGradientBitmap = Bitmap.createScaledBitmap(mGradientBitmap,
+                (int) (mGradientBitmap.getWidth() * mDisplayRatio),
+                (int) (mGradientBitmap.getHeight() * mDisplayRatio),
+                true);
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.view.SurfaceView#onMeasure(int, int)
+     */
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = 0;
+        int height = 0;
+
+        Display display = mWindowManager.getDefaultDisplay();
+        display.getSize(mSize);
+        int displayWidth = mSize.x;
+        int displayHeight = mSize.y;
+        displayWidth = getWidth();
+        displayHeight = getHeight();
+
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+        Log.d(TAG, "widthSpecSize=" + widthSpecSize + ", heightSpecSize=" + heightSpecSize);
+
+        if (widthSpecMode == MeasureSpec.EXACTLY) {
+            width = widthSpecSize;
+        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
+            width = Math.min(displayWidth, widthSpecSize);
+        } else {
+            width = displayWidth;
+        }
+
+        if (heightSpecMode == MeasureSpec.EXACTLY) {
+            height = heightSpecSize;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            height = Math.min(displayHeight, heightSpecSize);
+        } else {
+            height = displayHeight;
+        }
+
+        setMeasuredDimension(width, height);
+
+        if (width > 0 && height > 0) {
+            loadBitmaps(width, height);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.view.View#onDraw(android.graphics.Canvas)
+     */
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int offset = getWidth() / 2 - getHeight() / 2;
+
+        // Draw the background
+        canvas.drawColor(Color.BLACK);
+        canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundPaint);
+
+        // Draw the brake/gas indicators
+        if (mAxes[AxesMapping.AXIS_BRAKE.ordinal()] > 0.0f) {
+            mGradientPaint.setAlpha((int) (mAxes[AxesMapping.AXIS_BRAKE.ordinal()] * 100) + 155);
+            canvas.drawBitmap(mGradientBitmap, offset + mLeftGradient[0]
+                    * getHeight(), mLeftGradient[1] * getHeight(), mGradientPaint);
+        }
+        if (mAxes[AxesMapping.AXIS_GAS.ordinal()] > 0.0f) {
+            mGradientPaint.setAlpha((int) (mAxes[AxesMapping.AXIS_GAS.ordinal()] * 100) + 155);
+            canvas.drawBitmap(mGradientBitmap, offset + mRightGradient[0]
+                    * getHeight(), mRightGradient[1] * getHeight(), mGradientPaint);
+        }
+
+        // Draw the paddles
+        canvas.drawColor(Color.TRANSPARENT);
+        if (mButtons[ButtonMapping.BUTTON_R1.ordinal()] == 0) {
+            canvas.drawBitmap(mRightPaddleBitmap, offset + mRightPaddle[0]
+                    * getHeight(), mRightPaddle[1] * getHeight(), mImagePaint);
+        } else if (mButtons[ButtonMapping.BUTTON_R1.ordinal()] == 1) {
+            canvas.drawBitmap(mRightPaddleBitmap, offset + mRightPaddlePressed[0]
+                    * getHeight(), mRightPaddlePressed[1] * getHeight(), mImagePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_L1.ordinal()] == 0) {
+            canvas.drawBitmap(mLeftPaddleBitmap, offset + mLeftPaddle[0]
+                    * getHeight(), mLeftPaddle[1] * getHeight(), mImagePaint);
+        }
+        else if (mButtons[ButtonMapping.BUTTON_L1.ordinal()] == 1) {
+            canvas.drawBitmap(mLeftPaddleBitmap, offset + mLeftPaddlePressed[0]
+                    * getHeight(), mLeftPaddlePressed[1] * getHeight(), mImagePaint);
+        }
+
+        // Draw the controller body
+        canvas.drawBitmap(mControllerBitmap, offset, 0, mImagePaint);
+
+        // Draw the axes
+        mAxisLeftX = offset + mLeftAxis[0] * getHeight();
+        mAxisLeftY = mLeftAxis[1] * getHeight();
+        mAxisRightX = offset + mRightAxis[0] * getHeight();
+        mAxisRightY = mRightAxis[1] * getHeight();
+        if (mAxes[AxesMapping.AXIS_X.ordinal()] != 0.0f) {
+            mAxisLeftX = mAxisLeftX + mLeftAxis[3] * getHeight()
+                    * mAxes[AxesMapping.AXIS_X.ordinal()];
+        }
+        if (mAxes[AxesMapping.AXIS_Y.ordinal()] != 0.0f) {
+            mAxisLeftY = mAxisLeftY + mLeftAxis[3]
+                    * getHeight() * mAxes[AxesMapping.AXIS_Y.ordinal()];
+        }
+        canvas.drawBitmap(mAxisBitmap, mAxisLeftX, mAxisLeftY, mImagePaint);
+        if (mAxes[AxesMapping.AXIS_Z.ordinal()] != 0.0f) {
+            mAxisRightX = mAxisRightX + mRightAxis[3] * getHeight()
+                    * mAxes[AxesMapping.AXIS_Z.ordinal()];
+        }
+        if (mAxes[AxesMapping.AXIS_RZ.ordinal()] != 0.0f) {
+            mAxisRightY = mAxisRightY + mRightAxis[3]
+                    * getHeight() * mAxes[AxesMapping.AXIS_RZ.ordinal()];
+        }
+        canvas.drawBitmap(mAxisBitmap, mAxisRightX, mAxisRightY, mImagePaint);
+
+        // Draw the LED light
+        if (mCurrentControllerNumber > 0 && mCurrentControllerNumber <= MAX_CONTROLLERS) {
+            canvas.drawBitmap(mBlueLedBitmap, offset
+                    + mLedButtons[2 * mCurrentControllerNumber - 2] * getHeight(),
+                    mLedButtons[2 * mCurrentControllerNumber - 1] * getHeight(), mLedPaint);
+        }
+
+        // Draw the directional buttons
+        if (mAxes[AxesMapping.AXIS_HAT_X.ordinal()] == 1.0f) {
+            canvas.drawBitmap(mRightDirectionalBitmap, offset + mRightDirectionalButton[0]
+                    * getHeight(),
+                    mRightDirectionalButton[1] * getHeight(), mDirectionalPaint);
+        }
+        if (mAxes[AxesMapping.AXIS_HAT_Y.ordinal()] == -1.0f) {
+            canvas.drawBitmap(mTopDirectionalBitmap, offset + mTopDirectionalButton[0]
+                    * getHeight(),
+                    mTopDirectionalButton[1] * getHeight(), mDirectionalPaint);
+        }
+        if (mAxes[AxesMapping.AXIS_HAT_X.ordinal()] == -1.0f) {
+            canvas.drawBitmap(mLeftDirectionalBitmap, offset + mLeftDirectionalButton[0]
+                    * getHeight(),
+                    mLeftDirectionalButton[1] * getHeight(), mDirectionalPaint);
+        }
+        if (mAxes[AxesMapping.AXIS_HAT_Y.ordinal()] == 1.0f) {
+            canvas.drawBitmap(mBottomDirectionalBitmap, offset + mBottomDirectionalButton[0]
+                    * getHeight(), mBottomDirectionalButton[1] * getHeight(), mDirectionalPaint);
+        }
+
+        // Draw the A/B/X/Y buttons
+        canvas.drawColor(Color.TRANSPARENT);
+        mCirclePaint.setColor(getResources().getColor(R.color.transparent_black));
+        if (mButtons[ButtonMapping.BUTTON_Y.ordinal()] == 1) {
+            canvas.drawCircle(offset + mYButton[0] * getHeight(), mYButton[1] * getHeight(),
+                    mYButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_X.ordinal()] == 1) {
+            canvas.drawCircle(offset + mXButton[0] * getHeight(), mXButton[1] * getHeight(),
+                    mXButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_B.ordinal()] == 1) {
+            canvas.drawCircle(offset + mBButton[0] * getHeight(), mBButton[1] * getHeight(),
+                    mBButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_A.ordinal()] == 1) {
+            canvas.drawCircle(offset + mAButton[0] * getHeight(), mAButton[1] * getHeight(),
+                    mAButton[2] * getHeight(), mCirclePaint);
+        }
+
+        // Draw the center buttons
+        if (mButtons[ButtonMapping.POWER.ordinal()] == 1) {
+            canvas.drawCircle(offset + mPowerButton[0] * getHeight(),
+                    mPowerButton[1] * getHeight(),
+                    mPowerButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_START.ordinal()] == 1) {
+            canvas.drawCircle(offset + mHomeButton[0] * getHeight(), mHomeButton[1] * getHeight(),
+                    mHomeButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BACK.ordinal()] == 1) {
+            canvas.drawCircle(offset + mBackButton[0] * getHeight(), mBackButton[1] * getHeight(),
+                    mBackButton[2] * getHeight(), mCirclePaint);
+        }
+
+        // Draw the axes
+        if (mButtons[ButtonMapping.BUTTON_THUMBL.ordinal()] == 1) {
+            canvas.drawCircle(mLeftAxisButton[2] * getHeight() + mAxisLeftX, mLeftAxisButton[2]
+                    * getHeight() + mAxisLeftY,
+                    mLeftAxisButton[2] * getHeight(), mCirclePaint);
+        }
+        if (mButtons[ButtonMapping.BUTTON_THUMBR.ordinal()] == 1) {
+            canvas.drawCircle(mRightAxisButton[2] * getHeight() + mAxisRightX, mRightAxisButton[2]
+                    * getHeight() + mAxisRightY,
+                    mRightAxisButton[2] * getHeight(), mCirclePaint);
+        }
+    }
+
+    /**
+     * Set the button and axes mapping data structures.
+     * 
+     * @param buttons
+     * @param axes
+     */
+    public void setButtonsAxes(int[] buttons, float[] axes) {
+        mButtons = buttons;
+        mAxes = axes;
+    }
+
+    public void setCurrentControllerNumber(int number) {
+        Log.d(TAG, "setCurrentControllerNumber: " + number);
+        mCurrentControllerNumber = number;
+    }
+}
diff --git a/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/FullscreenActivity.java b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/FullscreenActivity.java
new file mode 100644
index 0000000..d043d89
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/FullscreenActivity.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2014 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.example.android.visualgamecontroller;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.example.android.visualgamecontroller.util.SystemUiHider;
+
+import java.util.ArrayList;
+
+/**
+ * An example full-screen activity that shows and hides the system UI (i.e.
+ * status bar and navigation/system bar) with user interaction.
+ * 
+ * @see SystemUiHider
+ */
+public class FullscreenActivity extends Activity implements InputDeviceListener {
+    private static final String TAG = "FullscreenActivity";
+
+    /**
+     * Whether or not the system UI should be auto-hidden after
+     * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
+     */
+    private static final boolean AUTO_HIDE = true;
+
+    /**
+     * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
+     * user interaction before hiding the system UI.
+     */
+    private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
+
+    /**
+     * If set, will toggle the system UI visibility upon interaction. Otherwise,
+     * will show the system UI visibility upon interaction.
+     */
+    private static final boolean TOGGLE_ON_CLICK = true;
+
+    /**
+     * The flags to pass to {@link SystemUiHider#getInstance}.
+     */
+    private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
+
+    /**
+     * The instance of the {@link SystemUiHider} for this activity.
+     */
+    private SystemUiHider mSystemUiHider;
+
+    private ControllerView mControllerView;
+
+    public enum ButtonMapping {
+        BUTTON_A(KeyEvent.KEYCODE_BUTTON_A),
+        BUTTON_B(KeyEvent.KEYCODE_BUTTON_B),
+        BUTTON_X(KeyEvent.KEYCODE_BUTTON_X),
+        BUTTON_Y(KeyEvent.KEYCODE_BUTTON_Y),
+        BUTTON_L1(KeyEvent.KEYCODE_BUTTON_L1),
+        BUTTON_R1(KeyEvent.KEYCODE_BUTTON_R1),
+        BUTTON_L2(KeyEvent.KEYCODE_BUTTON_L2),
+        BUTTON_R2(KeyEvent.KEYCODE_BUTTON_R2),
+        BUTTON_SELECT(KeyEvent.KEYCODE_BUTTON_SELECT),
+        BUTTON_START(KeyEvent.KEYCODE_BUTTON_START),
+        BUTTON_THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL),
+        BUTTON_THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR),
+        BACK(KeyEvent.KEYCODE_BACK),
+        POWER(KeyEvent.KEYCODE_BUTTON_MODE);
+
+        private final int mKeyCode;
+
+        ButtonMapping(int keyCode) {
+            mKeyCode = keyCode;
+        }
+
+        private int getKeycode() {
+            return mKeyCode;
+        }
+    }
+
+    public enum AxesMapping {
+        AXIS_X(MotionEvent.AXIS_X),
+        AXIS_Y(MotionEvent.AXIS_Y),
+        AXIS_Z(MotionEvent.AXIS_Z),
+        AXIS_RZ(MotionEvent.AXIS_RZ),
+        AXIS_HAT_X(MotionEvent.AXIS_HAT_X),
+        AXIS_HAT_Y(MotionEvent.AXIS_HAT_Y),
+        AXIS_LTRIGGER(MotionEvent.AXIS_LTRIGGER),
+        AXIS_RTRIGGER(MotionEvent.AXIS_RTRIGGER),
+        AXIS_BRAKE(MotionEvent.AXIS_BRAKE),
+        AXIS_GAS(MotionEvent.AXIS_GAS);
+
+        private final int mMotionEvent;
+
+        AxesMapping(int motionEvent) {
+            mMotionEvent = motionEvent;
+        }
+
+        private int getMotionEvent() {
+            return mMotionEvent;
+        }
+    }
+
+    private int[] mButtons = new int[ButtonMapping.values().length];
+    private float[] mAxes = new float[AxesMapping.values().length];
+    private InputManager mInputManager;
+    private ArrayList<Integer> mConnectedDevices = new ArrayList<Integer>();
+    private int mCurrentDeviceId = -1;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        setContentView(R.layout.activity_fullscreen);
+
+        final View controlsView = findViewById(R.id.fullscreen_content_controls);
+        final View contentView = findViewById(R.id.fullscreen_content);
+
+        mControllerView = (ControllerView) findViewById(R.id.controller);
+        for (int i = 0; i < mButtons.length; i++) {
+            mButtons[i] = 0;
+        }
+        for (int i = 0; i < mAxes.length; i++) {
+            mAxes[i] = 0.0f;
+        }
+        mControllerView.setButtonsAxes(mButtons, mAxes);
+
+        // Set up an instance of SystemUiHider to control the system UI for
+        // this activity.
+        mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
+        mSystemUiHider.setup();
+        mSystemUiHider
+                .setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
+                    // Cached values.
+                    int mControlsHeight;
+                    int mShortAnimTime;
+
+                    @Override
+                    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+                    public void onVisibilityChange(boolean visible) {
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+                            // If the ViewPropertyAnimator API is available
+                            // (Honeycomb MR2 and later), use it to animate the
+                            // in-layout UI controls at the bottom of the
+                            // screen.
+                            if (mControlsHeight == 0) {
+                                mControlsHeight = controlsView.getHeight();
+                            }
+                            if (mShortAnimTime == 0) {
+                                mShortAnimTime = getResources().getInteger(
+                                        android.R.integer.config_shortAnimTime);
+                            }
+                            controlsView.animate()
+                                    .translationY(visible ? 0 : mControlsHeight)
+                                    .setDuration(mShortAnimTime);
+                        } else {
+                            // If the ViewPropertyAnimator APIs aren't
+                            // available, simply show or hide the in-layout UI
+                            // controls.
+                            controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
+                        }
+
+                        if (visible && AUTO_HIDE) {
+                            // Schedule a hide().
+                            delayedHide(AUTO_HIDE_DELAY_MILLIS);
+                        }
+                    }
+                });
+
+        // Set up the user interaction to manually show or hide the system UI.
+        contentView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (TOGGLE_ON_CLICK) {
+                    mSystemUiHider.toggle();
+                } else {
+                    mSystemUiHider.show();
+                }
+            }
+        });
+
+        mInputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
+        checkGameControllers();
+    }
+
+    /**
+     * Check for any game controllers that are connected already.
+     */
+    private void checkGameControllers() {
+        Log.d(TAG, "checkGameControllers");
+        int[] deviceIds = mInputManager.getInputDeviceIds();
+        for (int deviceId : deviceIds) {
+            InputDevice dev = InputDevice.getDevice(deviceId);
+            int sources = dev.getSources();
+
+            // Verify that the device has gamepad buttons, control sticks, or
+            // both.
+            if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
+                    || ((sources & InputDevice.SOURCE_JOYSTICK)
+                        == InputDevice.SOURCE_JOYSTICK)) {
+                // This device is a game controller. Store its device ID.
+                if (!mConnectedDevices.contains(deviceId)) {
+                    mConnectedDevices.add(deviceId);
+                    if (mCurrentDeviceId == -1) {
+                        mCurrentDeviceId = deviceId;
+                        mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
+                        mControllerView.invalidate();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+
+        // Trigger the initial hide() shortly after the activity has been
+        // created, to briefly hint to the user that UI controls
+        // are available.
+        delayedHide(100);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mInputManager.registerInputDeviceListener(this, null);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mInputManager.unregisterInputDeviceListener(this);
+    }
+
+    /**
+     * Touch listener to use for in-layout UI controls to delay hiding the
+     * system UI. This is to prevent the jarring behavior of controls going away
+     * while interacting with activity UI.
+     */
+    View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View view, MotionEvent motionEvent) {
+            if (AUTO_HIDE) {
+                delayedHide(AUTO_HIDE_DELAY_MILLIS);
+            }
+            return false;
+        }
+    };
+
+    Handler mHideHandler = new Handler();
+    Runnable mHideRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mSystemUiHider.hide();
+        }
+    };
+
+    /**
+     * Schedules a call to hide() in [delay] milliseconds, canceling any
+     * previously scheduled calls.
+     */
+    private void delayedHide(int delayMillis) {
+        mHideHandler.removeCallbacks(mHideRunnable);
+        mHideHandler.postDelayed(mHideRunnable, delayMillis);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.app.Activity#onGenericMotionEvent(android.view.MotionEvent)
+     */
+    @Override
+    public boolean onGenericMotionEvent(final MotionEvent ev) {
+        // Log.d(TAG, "onGenericMotionEvent: " + ev);
+        InputDevice device = ev.getDevice();
+        // Only care about game controllers.
+        if (device != null && device.getId() == mCurrentDeviceId) {
+            if (isGamepad(device)) {
+                for (AxesMapping axesMapping : AxesMapping.values()) {
+                    mAxes[axesMapping.ordinal()] = getCenteredAxis(ev, device,
+                            axesMapping.getMotionEvent());
+                }
+                mControllerView.invalidate();
+                return true;
+            }
+        }
+        return super.onGenericMotionEvent(ev);
+    }
+
+    /**
+     * Get centered position for axis input by considering flat area.
+     * 
+     * @param event
+     * @param device
+     * @param axis
+     * @return
+     */
+    private float getCenteredAxis(MotionEvent event, InputDevice device, int axis) {
+        InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
+
+        // A joystick at rest does not always report an absolute position of
+        // (0,0). Use the getFlat() method to determine the range of values
+        // bounding the joystick axis center.
+        if (range != null) {
+            float flat = range.getFlat();
+            float value = event.getAxisValue(axis);
+
+            // Ignore axis values that are within the 'flat' region of the
+            // joystick axis center.
+            if (Math.abs(value) > flat) {
+                return value;
+            }
+        }
+        return 0;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.support.v4.app.FragmentActivity#onKeyDown(int,
+     * android.view.KeyEvent)
+     */
+    @Override
+    public boolean onKeyDown(final int keyCode, KeyEvent ev) {
+        // Log.d(TAG, "onKeyDown: " + ev);
+        InputDevice device = ev.getDevice();
+        // Only care about game controllers.
+        if (device != null && device.getId() == mCurrentDeviceId) {
+            if (isGamepad(device)) {
+                int index = getButtonMappingIndex(keyCode);
+                if (index >= 0) {
+                    mButtons[index] = 1;
+                    mControllerView.invalidate();
+                }
+                return true;
+            }
+        }
+        return super.onKeyDown(keyCode, ev);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
+     */
+    @Override
+    public boolean onKeyUp(final int keyCode, KeyEvent ev) {
+        // Log.d(TAG, "onKeyUp: " + ev);
+        InputDevice device = ev.getDevice();
+        // Only care about game controllers.
+        if (device != null && device.getId() == mCurrentDeviceId) {
+            if (isGamepad(device)) {
+                int index = getButtonMappingIndex(keyCode);
+                if (index >= 0) {
+                    mButtons[index] = 0;
+                    mControllerView.invalidate();
+                }
+                return true;
+            }
+        }
+        return super.onKeyUp(keyCode, ev);
+    }
+
+    /**
+     * Utility method to determine if input device is a gamepad.
+     * 
+     * @param device
+     * @return
+     */
+    private boolean isGamepad(InputDevice device) {
+        if ((device.getSources() &
+                InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
+                || (device.getSources() &
+                InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get the array index for the key code.
+     * 
+     * @param keyCode
+     * @return
+     */
+    private int getButtonMappingIndex(int keyCode) {
+        for (ButtonMapping buttonMapping : ButtonMapping.values()) {
+            if (buttonMapping.getKeycode() == keyCode) {
+                return buttonMapping.ordinal();
+            }
+        }
+        return -1;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see
+     * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded
+     * (int)
+     */
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        Log.d(TAG, "onInputDeviceAdded: " + deviceId);
+        if (!mConnectedDevices.contains(deviceId)) {
+            mConnectedDevices.add(new Integer(deviceId));
+        }
+        if (mCurrentDeviceId == -1) {
+            mCurrentDeviceId = deviceId;
+            InputDevice dev = InputDevice.getDevice(mCurrentDeviceId);
+            if (dev != null) {
+                mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
+                mControllerView.invalidate();
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see
+     * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved
+     * (int)
+     */
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        Log.d(TAG, "onInputDeviceRemoved: " + deviceId);
+        mConnectedDevices.remove(new Integer(deviceId));
+        if (mCurrentDeviceId == deviceId) {
+            mCurrentDeviceId = -1;
+        }
+        if (mConnectedDevices.size() == 0) {
+            mControllerView.setCurrentControllerNumber(-1);
+            mControllerView.invalidate();
+        } else {
+            mCurrentDeviceId = mConnectedDevices.get(0);
+            InputDevice dev = InputDevice.getDevice(mCurrentDeviceId);
+            if (dev != null) {
+                mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
+                mControllerView.invalidate();
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see
+     * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged
+     * (int)
+     */
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        Log.d(TAG, "onInputDeviceChanged: " + deviceId);
+        mControllerView.invalidate();
+    }
+
+}
diff --git a/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHider.java b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHider.java
new file mode 100644
index 0000000..dba4867
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHider.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 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.example.android.visualgamecontroller.util;
+
+import android.app.Activity;
+import android.os.Build;
+import android.view.View;
+
+/**
+ * A utility class that helps with showing and hiding system UI such as the
+ * status bar and navigation/system bar. This class uses backward-compatibility
+ * techniques described in <a href=
+ * "http://developer.android.com/training/backward-compatible-ui/index.html">
+ * Creating Backward-Compatible UIs</a> to ensure that devices running any
+ * version of Android OS are supported. More specifically, there are separate
+ * implementations of this abstract class: for newer devices,
+ * {@link #getInstance} will return a {@link SystemUiHiderHoneycomb} instance,
+ * while on older devices {@link #getInstance} will return a
+ * {@link SystemUiHiderBase} instance.
+ * <p>
+ * For more on system bars, see <a href=
+ * "http://developer.android.com/design/get-started/ui-overview.html#system-bars"
+ * > System Bars</a>.
+ *
+ * @see android.view.View#setSystemUiVisibility(int)
+ * @see android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN
+ */
+public abstract class SystemUiHider {
+    /**
+     * When this flag is set, the
+     * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
+     * flag will be set on older devices, making the status bar "float" on top
+     * of the activity layout. This is most useful when there are no controls at
+     * the top of the activity layout.
+     * <p>
+     * This flag isn't used on newer devices because the <a
+     * href="http://developer.android.com/design/patterns/actionbar.html">action
+     * bar</a>, the most important structural element of an Android app, should
+     * be visible and not obscured by the system UI.
+     */
+    public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
+
+    /**
+     * When this flag is set, {@link #show()} and {@link #hide()} will toggle
+     * the visibility of the status bar. If there is a navigation bar, show and
+     * hide will toggle low profile mode.
+     */
+    public static final int FLAG_FULLSCREEN = 0x2;
+
+    /**
+     * When this flag is set, {@link #show()} and {@link #hide()} will toggle
+     * the visibility of the navigation bar, if it's present on the device and
+     * the device allows hiding it. In cases where the navigation bar is present
+     * but cannot be hidden, show and hide will toggle low profile mode.
+     */
+    public static final int FLAG_HIDE_NAVIGATION = FLAG_FULLSCREEN | 0x4;
+
+    /**
+     * The activity associated with this UI hider object.
+     */
+    protected Activity mActivity;
+
+    /**
+     * The view on which {@link View#setSystemUiVisibility(int)} will be called.
+     */
+    protected View mAnchorView;
+
+    /**
+     * The current UI hider flags.
+     *
+     * @see #FLAG_FULLSCREEN
+     * @see #FLAG_HIDE_NAVIGATION
+     * @see #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES
+     */
+    protected int mFlags;
+
+    /**
+     * The current visibility callback.
+     */
+    protected OnVisibilityChangeListener mOnVisibilityChangeListener = sDummyListener;
+
+    /**
+     * Creates and returns an instance of {@link SystemUiHider} that is
+     * appropriate for this device. The object will be either a
+     * {@link SystemUiHiderBase} or {@link SystemUiHiderHoneycomb} depending on
+     * the device.
+     *
+     * @param activity The activity whose window's system UI should be
+     *            controlled by this class.
+     * @param anchorView The view on which
+     *            {@link View#setSystemUiVisibility(int)} will be called.
+     * @param flags Either 0 or any combination of {@link #FLAG_FULLSCREEN},
+     *            {@link #FLAG_HIDE_NAVIGATION}, and
+     *            {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES}.
+     */
+    public static SystemUiHider getInstance(Activity activity, View anchorView, int flags) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            return new SystemUiHiderHoneycomb(activity, anchorView, flags);
+        } else {
+            return new SystemUiHiderBase(activity, anchorView, flags);
+        }
+    }
+
+    protected SystemUiHider(Activity activity, View anchorView, int flags) {
+        mActivity = activity;
+        mAnchorView = anchorView;
+        mFlags = flags;
+    }
+
+    /**
+     * Sets up the system UI hider. Should be called from
+     * {@link Activity#onCreate}.
+     */
+    public abstract void setup();
+
+    /**
+     * Returns whether or not the system UI is visible.
+     */
+    public abstract boolean isVisible();
+
+    /**
+     * Hide the system UI.
+     */
+    public abstract void hide();
+
+    /**
+     * Show the system UI.
+     */
+    public abstract void show();
+
+    /**
+     * Toggle the visibility of the system UI.
+     */
+    public void toggle() {
+        if (isVisible()) {
+            hide();
+        } else {
+            show();
+        }
+    }
+
+    /**
+     * Registers a callback, to be triggered when the system UI visibility
+     * changes.
+     */
+    public void setOnVisibilityChangeListener(OnVisibilityChangeListener listener) {
+        if (listener == null) {
+            listener = sDummyListener;
+        }
+
+        mOnVisibilityChangeListener = listener;
+    }
+
+    /**
+     * A dummy no-op callback for use when there is no other listener set.
+     */
+    private static OnVisibilityChangeListener sDummyListener = new OnVisibilityChangeListener() {
+        @Override
+        public void onVisibilityChange(boolean visible) {
+        }
+    };
+
+    /**
+     * A callback interface used to listen for system UI visibility changes.
+     */
+    public interface OnVisibilityChangeListener {
+        /**
+         * Called when the system UI visibility has changed.
+         *
+         * @param visible True if the system UI is visible.
+         */
+        public void onVisibilityChange(boolean visible);
+    }
+}
diff --git a/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderBase.java b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderBase.java
new file mode 100644
index 0000000..ec0b6d0
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderBase.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.example.android.visualgamecontroller.util;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * A base implementation of {@link SystemUiHider}. Uses APIs available in all
+ * API levels to show and hide the status bar.
+ */
+public class SystemUiHiderBase extends SystemUiHider {
+    /**
+     * Whether or not the system UI is currently visible. This is a cached value
+     * from calls to {@link #hide()} and {@link #show()}.
+     */
+    private boolean mVisible = true;
+
+    /**
+     * Constructor not intended to be called by clients. Use
+     * {@link SystemUiHider#getInstance} to obtain an instance.
+     */
+    protected SystemUiHiderBase(Activity activity, View anchorView, int flags) {
+        super(activity, anchorView, flags);
+    }
+
+    @Override
+    public void setup() {
+        if ((mFlags & FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) == 0) {
+            mActivity.getWindow().setFlags(
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+        }
+    }
+
+    @Override
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    @Override
+    public void hide() {
+        if ((mFlags & FLAG_FULLSCREEN) != 0) {
+            mActivity.getWindow().setFlags(
+                    WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+        mOnVisibilityChangeListener.onVisibilityChange(false);
+        mVisible = false;
+    }
+
+    @Override
+    public void show() {
+        if ((mFlags & FLAG_FULLSCREEN) != 0) {
+            mActivity.getWindow().setFlags(
+                    0,
+                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+        mOnVisibilityChangeListener.onVisibilityChange(true);
+        mVisible = true;
+    }
+}
diff --git a/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderHoneycomb.java b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderHoneycomb.java
new file mode 100644
index 0000000..7b2b279
--- /dev/null
+++ b/prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderHoneycomb.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 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.example.android.visualgamecontroller.util;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.os.Build;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * An API 11+ implementation of {@link SystemUiHider}. Uses APIs available in
+ * Honeycomb and later (specifically {@link View#setSystemUiVisibility(int)}) to
+ * show and hide the system UI.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class SystemUiHiderHoneycomb extends SystemUiHiderBase {
+    /**
+     * Flags for {@link View#setSystemUiVisibility(int)} to use when showing the
+     * system UI.
+     */
+    private int mShowFlags;
+
+    /**
+     * Flags for {@link View#setSystemUiVisibility(int)} to use when hiding the
+     * system UI.
+     */
+    private int mHideFlags;
+
+    /**
+     * Flags to test against the first parameter in
+     * {@link android.view.View.OnSystemUiVisibilityChangeListener#onSystemUiVisibilityChange(int)}
+     * to determine the system UI visibility state.
+     */
+    private int mTestFlags;
+
+    /**
+     * Whether or not the system UI is currently visible. This is cached from
+     * {@link android.view.View.OnSystemUiVisibilityChangeListener}.
+     */
+    private boolean mVisible = true;
+
+    /**
+     * Constructor not intended to be called by clients. Use
+     * {@link SystemUiHider#getInstance} to obtain an instance.
+     */
+    protected SystemUiHiderHoneycomb(Activity activity, View anchorView, int flags) {
+        super(activity, anchorView, flags);
+
+        mShowFlags = View.SYSTEM_UI_FLAG_VISIBLE;
+        mHideFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
+        mTestFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
+
+        if ((mFlags & FLAG_FULLSCREEN) != 0) {
+            // If the client requested fullscreen, add flags relevant to hiding
+            // the status bar. Note that some of these constants are new as of
+            // API 16 (Jelly Bean). It is safe to use them, as they are inlined
+            // at compile-time and do nothing on pre-Jelly Bean devices.
+            mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
+        }
+
+        if ((mFlags & FLAG_HIDE_NAVIGATION) != 0) {
+            // If the client requested hiding navigation, add relevant flags.
+            mShowFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+            mHideFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+            mTestFlags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setup() {
+        mAnchorView.setOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void hide() {
+        mAnchorView.setSystemUiVisibility(mHideFlags);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void show() {
+        mAnchorView.setSystemUiVisibility(mShowFlags);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    private View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener = new View.OnSystemUiVisibilityChangeListener() {
+        @Override
+        public void onSystemUiVisibilityChange(int vis) {
+            // Test against mTestFlags to see if the system UI is visible.
+            if ((vis & mTestFlags) != 0) {
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+                    // Pre-Jelly Bean, we must manually hide the action bar
+                    // and use the old window flags API.
+                    mActivity.getActionBar().hide();
+                    mActivity.getWindow().setFlags(
+                            WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                            WindowManager.LayoutParams.FLAG_FULLSCREEN);
+                }
+
+                // Trigger the registered listener and cache the visibility
+                // state.
+                mOnVisibilityChangeListener.onVisibilityChange(false);
+                mVisible = false;
+
+            } else {
+                mAnchorView.setSystemUiVisibility(mShowFlags);
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+                    // Pre-Jelly Bean, we must manually show the action bar
+                    // and use the old window flags API.
+                    mActivity.getActionBar().show();
+                    mActivity.getWindow().setFlags(
+                            0,
+                            WindowManager.LayoutParams.FLAG_FULLSCREEN);
+                }
+
+                // Trigger the registered listener and cache the visibility
+                // state.
+                mOnVisibilityChangeListener.onVisibilityChange(true);
+                mVisible = true;
+            }
+        }
+    };
+}