diff options
author | Trevor Johns <trevorjohns@google.com> | 2014-06-19 23:24:45 -0700 |
---|---|---|
committer | Trevor Johns <trevorjohns@google.com> | 2014-06-20 12:10:01 -0700 |
commit | c4d25c52f44c0c003327abe2dd1ec088dd894970 (patch) | |
tree | f5f236234438f15bcfd56cad3f19c90c27199567 /prebuilts/androidtv | |
parent | 3f12967ed90fc96bc8f22e5d28d0553bafde99e5 (diff) | |
download | build-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')
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 Binary files differnew file mode 100644 index 00000000..fda9a74a --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum.png 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 Binary files differnew file mode 100644 index 00000000..498cf669 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/app_icon_quantum_card.png 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 Binary files differnew file mode 100644 index 00000000..c9c7828b --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_av_play_dark.png diff --git a/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..96a442e5 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..4cedb526 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_banner.png 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 Binary files differnew file mode 100644 index 00000000..20fd898d --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-hdpi/videos_by_google_icon.png diff --git a/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..99238729 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-ldpi/ic_launcher.png diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png Binary files differnew file mode 100644 index 00000000..6b621385 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum.png 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 Binary files differnew file mode 100644 index 00000000..ac9cc307 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/app_icon_quantum_card.png 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 Binary files differnew file mode 100644 index 00000000..6270d656 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_av_pause_dark.png diff --git a/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..359047df --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..b1916262 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_banner.png 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 Binary files differnew file mode 100644 index 00000000..8a7c6dc1 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-mdpi/videos_by_google_icon.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png Binary files differnew file mode 100644 index 00000000..825ef637 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum.png 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 Binary files differnew file mode 100644 index 00000000..9b1703d8 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/app_icon_quantum_card.png 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 Binary files differnew file mode 100644 index 00000000..29f4e01d --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/card_background_default.9.png 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 Binary files differnew file mode 100644 index 00000000..476c698e --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/grid_bg.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..71c6d760 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..63b45b94 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png 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 Binary files differnew file mode 100644 index 00000000..9cf25826 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_normal.png 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 Binary files differnew file mode 100644 index 00000000..516ceca7 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png 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 Binary files differnew file mode 100644 index 00000000..bf93814a --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_focussed.png 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 Binary files differnew file mode 100644 index 00000000..966f754b --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_normal.png 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 Binary files differnew file mode 100644 index 00000000..934ed894 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_action_pressed.png 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 Binary files differnew file mode 100644 index 00000000..687b4218 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_focussed.png 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 Binary files differnew file mode 100644 index 00000000..1eadfa47 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_normal.png 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 Binary files differnew file mode 100644 index 00000000..7f583f61 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/ic_play_playcontrol_pressed.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png Binary files differnew file mode 100644 index 00000000..9bb7247d --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_disabled.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png Binary files differnew file mode 100644 index 00000000..22898599 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_focussed.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png Binary files differnew file mode 100644 index 00000000..34aff7ce --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_normal.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png Binary files differnew file mode 100644 index 00000000..3c492e16 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/scrubber_pressed.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png b/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png Binary files differnew file mode 100644 index 00000000..6d00d096 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/shadow7.9.png 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 Binary files differnew file mode 100644 index 00000000..bdcf41e4 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_banner.png 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 Binary files differnew file mode 100644 index 00000000..9bc48360 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xhdpi/videos_by_google_icon.png diff --git a/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png Binary files differnew file mode 100644 index 00000000..c82f94cd --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum.png 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 Binary files differnew file mode 100644 index 00000000..6c50e8ff --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/app_icon_quantum_card.png 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 Binary files differnew file mode 100644 index 00000000..6c121e61 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_banner.png 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 Binary files differnew file mode 100644 index 00000000..4258160e --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable-xxhdpi/videos_by_google_icon.png diff --git a/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png Binary files differnew file mode 100644 index 00000000..fda9a74a --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum.png diff --git a/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png Binary files differnew file mode 100644 index 00000000..498cf669 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/app_icon_quantum_card.png diff --git a/prebuilts/androidtv/leanback/res/drawable/details_img.png b/prebuilts/androidtv/leanback/res/drawable/details_img.png Binary files differnew file mode 100644 index 00000000..7ea688b6 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/details_img.png diff --git a/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png b/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png Binary files differnew file mode 100644 index 00000000..3d555efa --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/ic_action_a.png diff --git a/prebuilts/androidtv/leanback/res/drawable/ic_title.png b/prebuilts/androidtv/leanback/res/drawable/ic_title.png Binary files differnew file mode 100644 index 00000000..1c62b2e1 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/ic_title.png diff --git a/prebuilts/androidtv/leanback/res/drawable/movie.png b/prebuilts/androidtv/leanback/res/drawable/movie.png Binary files differnew file mode 100644 index 00000000..cb5cb6d3 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/movie.png 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 Binary files differnew file mode 100644 index 00000000..6d00d096 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/shadow7.9.png diff --git a/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png Binary files differnew file mode 100644 index 00000000..4cedb526 --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_banner.png diff --git a/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png Binary files differnew file mode 100644 index 00000000..20fd898d --- /dev/null +++ b/prebuilts/androidtv/leanback/res/drawable/videos_by_google_icon.png 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…</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 Binary files differnew file mode 100644 index 00000000..58385981 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/gradle/wrapper/gradle-wrapper.jar 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 Binary files differnew file mode 100644 index 00000000..a18cbb48 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/ic_launcher-web.png 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 Binary files differnew file mode 100644 index 00000000..613cac2b --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-hdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..39b981f1 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-mdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..4d4ccbbd --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xhdpi/ic_launcher.png diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png Binary files differnew file mode 100644 index 00000000..e22f1ef1 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/axis.png diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png Binary files differnew file mode 100644 index 00000000..5df2ce88 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional.png 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 Binary files differnew file mode 100644 index 00000000..4fec6bb0 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_bottom.png 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 Binary files differnew file mode 100644 index 00000000..15d2b421 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_left.png 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 Binary files differnew file mode 100644 index 00000000..b0671340 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_right.png 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 Binary files differnew file mode 100644 index 00000000..9c307a28 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/directional_top.png 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 Binary files differnew file mode 100644 index 00000000..50301efa --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller.png 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 Binary files differnew file mode 100644 index 00000000..758854a0 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_axis.png 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 Binary files differnew file mode 100644 index 00000000..ef3d290e --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_original.png 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 Binary files differnew file mode 100644 index 00000000..977ac598 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/game_controller_paddles.png diff --git a/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png Binary files differnew file mode 100644 index 00000000..b14e842c --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/gradient.png 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 Binary files differnew file mode 100644 index 00000000..6357c2c9 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 00000000..0574d2d6 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/led_blue.png 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 Binary files differnew file mode 100644 index 00000000..28ff638a --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/left_paddle.png 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 Binary files differnew file mode 100644 index 00000000..692041f6 --- /dev/null +++ b/prebuilts/androidtv/visual-game-controller/res/drawable-xxhdpi/right_paddle.png 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; + } + } + }; +} |