summaryrefslogtreecommitdiff
path: root/prebuilts/androidtv
diff options
context:
space:
mode:
authorTrevor Johns <trevorjohns@google.com>2014-06-19 23:24:45 -0700
committerTrevor Johns <trevorjohns@google.com>2014-06-20 12:10:01 -0700
commitc4d25c52f44c0c003327abe2dd1ec088dd894970 (patch)
treef5f236234438f15bcfd56cad3f19c90c27199567 /prebuilts/androidtv
parent3f12967ed90fc96bc8f22e5d28d0553bafde99e5 (diff)
downloadbuild-c4d25c52f44c0c003327abe2dd1ec088dd894970.tar.gz
Add AndroidTV prebuilds for LMP Preview release
These files are coming from different repositories, so we're storing them here temporarily until we have a better integration in place. Change-Id: I7f396764879a68ed1344da76a203a2f15f4a7b80
Diffstat (limited to 'prebuilts/androidtv')
-rw-r--r--prebuilts/androidtv/leanback/AndroidManifest.xml82
-rw-r--r--prebuilts/androidtv/leanback/CONTRIBUTING.md56
-rw-r--r--prebuilts/androidtv/leanback/LICENSE191
-rw-r--r--prebuilts/androidtv/leanback/README.md26
-rw-r--r--prebuilts/androidtv/leanback/project.properties16
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum.pngbin0 -> 12792 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum_card.pngbin0 -> 1003 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/ic_av_play_dark.pngbin0 -> 1413 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.pngbin0 -> 9397 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_banner.pngbin0 -> 6958 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_icon.pngbin0 -> 707 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.pngbin0 -> 2729 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.pngbin0 -> 8820 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum_card.pngbin0 -> 750 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/ic_av_pause_dark.pngbin0 -> 1070 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_banner.pngbin0 -> 4658 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_icon.pngbin0 -> 535 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.pngbin0 -> 11455 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum_card.pngbin0 -> 1365 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/card_background_default.9.pngbin0 -> 14923 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/default_background.xml9
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/grid_bg.pngbin0 -> 400386 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_focussed.pngbin0 -> 1004 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_normal.pngbin0 -> 677 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_pressed.pngbin0 -> 885 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_focussed.pngbin0 -> 1667 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_normal.pngbin0 -> 1195 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_pressed.pngbin0 -> 1181 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_focussed.pngbin0 -> 1659 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_normal.pngbin0 -> 1376 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_pressed.pngbin0 -> 1943 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.pngbin0 -> 1229 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.pngbin0 -> 1137 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.pngbin0 -> 1331 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.pngbin0 -> 2430 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.pngbin0 -> 233 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_banner.pngbin0 -> 5623 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_icon.pngbin0 -> 882 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.pngbin0 -> 17757 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum_card.pngbin0 -> 1297 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_banner.pngbin0 -> 9324 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_icon.pngbin0 -> 632 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.pngbin0 -> 12792 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.pngbin0 -> 1003 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/details_img.pngbin0 -> 2143 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/ic_action_a.pngbin0 -> 720 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/ic_title.pngbin0 -> 243 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/movie.pngbin0 -> 13007 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/player_bg_gradient_dark.xml11
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/shadow7.9.pngbin0 -> 233 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.pngbin0 -> 6958 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.pngbin0 -> 707 bytes
-rw-r--r--prebuilts/androidtv/leanback/res/layout/details.xml23
-rw-r--r--prebuilts/androidtv/leanback/res/layout/main.xml23
-rw-r--r--prebuilts/androidtv/leanback/res/layout/movie_card.xml22
-rw-r--r--prebuilts/androidtv/leanback/res/layout/player_activity.xml86
-rw-r--r--prebuilts/androidtv/leanback/res/layout/search.xml23
-rw-r--r--prebuilts/androidtv/leanback/res/layout/vertical_grid.xml23
-rw-r--r--prebuilts/androidtv/leanback/res/values/colors.xml18
-rw-r--r--prebuilts/androidtv/leanback/res/values/strings.xml74
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/BootupActivity.java52
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/CardPresenter.java136
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsActivity.java35
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/DetailsDescriptionPresenter.java31
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/LeanbackDetailsFragment.java193
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/MainActivity.java33
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/MainFragment.java294
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/Movie.java143
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/PicassoBackgroundManagerTarget.java67
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/PlayerActivity.java451
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/RecommendationBuilder.java153
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchActivity.java32
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/SearchFragment.java135
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/UpdateRecommendationsService.java93
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/Utils.java98
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridActivity.java33
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/VerticalGridFragment.java93
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoItemLoader.java66
-rw-r--r--prebuilts/androidtv/leanback/src/com/example/android/leanback/VideoProvider.java180
-rw-r--r--prebuilts/androidtv/visual-game-controller/AndroidManifest.xml45
-rw-r--r--prebuilts/androidtv/visual-game-controller/CONTRIBUTING.md56
-rw-r--r--prebuilts/androidtv/visual-game-controller/LICENSE191
-rw-r--r--prebuilts/androidtv/visual-game-controller/README.md24
-rw-r--r--prebuilts/androidtv/visual-game-controller/build.gradle36
-rw-r--r--prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.jarbin0 -> 50557 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xprebuilts/androidtv/visual-game-controller/gradlew164
-rw-r--r--prebuilts/androidtv/visual-game-controller/gradlew.bat90
-rw-r--r--prebuilts/androidtv/visual-game-controller/ic_launcher-web.pngbin0 -> 51394 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/proguard-project.txt20
-rw-r--r--prebuilts/androidtv/visual-game-controller/project.properties15
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-hdpi/ic_launcher.pngbin0 -> 6320 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-mdpi/ic_launcher.pngbin0 -> 3243 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xhdpi/ic_launcher.pngbin0 -> 8231 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.pngbin0 -> 14837 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.pngbin0 -> 819 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_bottom.pngbin0 -> 744 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_left.pngbin0 -> 809 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_right.pngbin0 -> 819 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_top.pngbin0 -> 734 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller.pngbin0 -> 383857 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_axis.pngbin0 -> 360796 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_original.pngbin0 -> 4820903 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_paddles.pngbin0 -> 360327 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.pngbin0 -> 15527 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 15980 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/led_blue.pngbin0 -> 288 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/left_paddle.pngbin0 -> 18844 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/right_paddle.pngbin0 -> 10624 bytes
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/layout/activity_fullscreen.xml75
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values-v11/styles.xml24
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values-v14/styles.xml12
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values/attrs.xml37
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values/colors.xml13
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values/strings.xml7
-rw-r--r--prebuilts/androidtv/visual-game-controller/res/values/styles.xml30
-rw-r--r--prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/ControllerView.java497
-rw-r--r--prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/FullscreenActivity.java476
-rw-r--r--prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHider.java188
-rw-r--r--prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderBase.java79
-rw-r--r--prebuilts/androidtv/visual-game-controller/src/com/example/android/visualgamecontroller/util/SystemUiHiderHoneycomb.java148
123 files changed, 5234 insertions, 0 deletions
diff --git a/prebuilts/androidtv/leanback/AndroidManifest.xml b/prebuilts/androidtv/leanback/AndroidManifest.xml
new file mode 100644
index 00000000..e3bf401a
--- /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 00000000..59e2c2f5
--- /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 00000000..8405e89a
--- /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 00000000..320e4f27
--- /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 00000000..70b2922d
--- /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 00000000..fda9a74a
--- /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 00000000..498cf669
--- /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 00000000..c9c7828b
--- /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 00000000..96a442e5
--- /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 00000000..4cedb526
--- /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 00000000..20fd898d
--- /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 00000000..99238729
--- /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 00000000..6b621385
--- /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 00000000..ac9cc307
--- /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 00000000..6270d656
--- /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 00000000..359047df
--- /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 00000000..b1916262
--- /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 00000000..8a7c6dc1
--- /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 00000000..825ef637
--- /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 00000000..9b1703d8
--- /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 00000000..29f4e01d
--- /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 00000000..07b05899
--- /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 00000000..476c698e
--- /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 00000000..71c6d760
--- /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 00000000..63b45b94
--- /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 00000000..9cf25826
--- /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 00000000..516ceca7
--- /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 00000000..bf93814a
--- /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 00000000..966f754b
--- /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 00000000..934ed894
--- /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 00000000..687b4218
--- /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 00000000..1eadfa47
--- /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 00000000..7f583f61
--- /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 00000000..9bb7247d
--- /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 00000000..22898599
--- /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 00000000..34aff7ce
--- /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 00000000..3c492e16
--- /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 00000000..6d00d096
--- /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 00000000..bdcf41e4
--- /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 00000000..9bc48360
--- /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 00000000..c82f94cd
--- /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 00000000..6c50e8ff
--- /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 00000000..6c121e61
--- /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 00000000..4258160e
--- /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 00000000..fda9a74a
--- /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 00000000..498cf669
--- /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 00000000..7ea688b6
--- /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 00000000..3d555efa
--- /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 00000000..1c62b2e1
--- /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 00000000..cb5cb6d3
--- /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 00000000..4450cb60
--- /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 00000000..6d00d096
--- /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 00000000..4cedb526
--- /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 00000000..20fd898d
--- /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 00000000..e7f675d9
--- /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 00000000..46eaad9d
--- /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 00000000..29bad931
--- /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 00000000..f976f7fa
--- /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 00000000..b65600ca
--- /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 00000000..1007042c
--- /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 00000000..1583f328
--- /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 00000000..3e47d390
--- /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 00000000..4e35e3db
--- /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 00000000..00afac59
--- /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 00000000..c35d1507
--- /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 00000000..39a83b9f
--- /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 00000000..21c3a70d
--- /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 00000000..a63a3c9d
--- /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 00000000..5092165f
--- /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 00000000..adc01a9e
--- /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 00000000..b8fa117f
--- /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 00000000..d2faf6f5
--- /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 00000000..7ef96bce
--- /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 00000000..0ce0fb96
--- /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 00000000..944309aa
--- /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 00000000..f0695e89
--- /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 00000000..7f660180
--- /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 00000000..05e490d5
--- /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 00000000..52737008
--- /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 00000000..108d1795
--- /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 00000000..cd7ebfd5
--- /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 00000000..565264e3
--- /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 00000000..59e2c2f5
--- /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 00000000..8405e89a
--- /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 00000000..d8ae3514
--- /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 00000000..ee65cfe8
--- /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 00000000..58385981
--- /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 00000000..f4c84175
--- /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 00000000..91a7e269
--- /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 00000000..aec99730
--- /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 00000000..a18cbb48
--- /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 00000000..f2fe1559
--- /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 00000000..a2573881
--- /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 00000000..613cac2b
--- /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 00000000..39b981f1
--- /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 00000000..4d4ccbbd
--- /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 00000000..e22f1ef1
--- /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 00000000..5df2ce88
--- /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 00000000..4fec6bb0
--- /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 00000000..15d2b421
--- /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 00000000..b0671340
--- /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 00000000..9c307a28
--- /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 00000000..50301efa
--- /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 00000000..758854a0
--- /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 00000000..ef3d290e
--- /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 00000000..977ac598
--- /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 00000000..b14e842c
--- /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 00000000..6357c2c9
--- /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 00000000..0574d2d6
--- /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 00000000..28ff638a
--- /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 00000000..692041f6
--- /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 00000000..ccc9a6c6
--- /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 00000000..5c4ba5f7
--- /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 00000000..664f4f16
--- /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 00000000..9f091eb4
--- /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 00000000..d4083306
--- /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 00000000..01a28c62
--- /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 00000000..b2cd8b02
--- /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 00000000..f487b0e9
--- /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 00000000..d043d891
--- /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 00000000..dba48674
--- /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 00000000..ec0b6d05
--- /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 00000000..7b2b279e
--- /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;
+ }
+ }
+ };
+}