diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..a01d6681
Binary files /dev/null and b/.DS_Store differ
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 1f670e75..00000000
--- a/.editorconfig
+++ /dev/null
@@ -1,7 +0,0 @@
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-end_of_line = LF
-trim_trailing_whitespace = true
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index c64b1259..00000000
--- a/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-node_modules
-npm-debug.log
-
-*~
-
-#OS X System Files
-.DS_Store
-
-# Xcode
-#
-build/
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
-xcuserdata
-*.xccheckout
-*.moved-aside
-DerivedData
-*.hmap
-*.ipa
-*.xcuserstate
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 1927412a..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,13 +0,0 @@
-[submodule "ios/PhyWeb/third-party/SDWebImage"]
- path = ios/PhyWeb/third-party/SDWebImage
- url = https://github.com/rs/SDWebImage
-[submodule "ios/PhyWeb/third-party/uribeacon"]
- path = ios/PhyWeb/third-party/uribeacon
- url = https://github.com/google/uribeacon
-[submodule "ios/PhyWeb/third-party/MBProgressHUD"]
- path = ios/PhyWeb/third-party/MBProgressHUD
- url = https://github.com/jdg/MBProgressHUD
-[submodule "ios/PhyWeb/third-party/SVPullToRefresh"]
- path = ios/PhyWeb/third-party/SVPullToRefresh
- url = https://github.com/dinhviethoa/SVPullToRefresh
- branch = contentinset-update
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 75765e9c..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: android
-jdk: oraclejdk7
-
-android:
- components:
- # The BuildTools version used by your project
- - build-tools-21.1.2
-
- # The SDK version used to compile your project
- - android-21
-
- # Use extra android repo locations to find the appcompat-v7:21.0.3 support library
- - extra
-
-#specify the build script to run. currently in the root directory along with .travis.yml file
-script: "./travis/build.sh"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 453c372c..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# How to contribute #
-
-We'd love to accept your patches and contributions to this project. There are
-just a few small guidelines you need to follow.
-
-
-## Contributor License Agreement ##
-
-Contributions to any Google project must be accompanied by a Contributor
-License Agreement. This is not a copyright **assignment**, it simply gives
-Google permission to use and redistribute your contributions as part of the
-project.
-
- * 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][].
-
- * If you work for a company that wants to allow you to contribute your work,
- then you'll need to sign a [corporate CLA][].
-
-You generally only need to submit a CLA once, so if you've already submitted
-one (even if it was for a different project), you probably don't need to do it
-again.
-
-[individual CLA]: https://developers.google.com/open-source/cla/individual
-[corporate CLA]: https://developers.google.com/open-source/cla/corporate
-
-Once your CLA is submitted (or if you already submitted one for
-another Google project), make a commit adding yourself to the
-[CONTRIBUTORS][] file. This commit can be part of your first [pull request][].
-
-[CONTRIBUTORS]: CONTRIBUTORS
-
-
-## Submitting a patch ##
-
- 1. It's generally best to start by opening a new issue describing the bug or
- feature you're intending to fix. Even if you think it's relatively minor,
- it's helpful to know what people are working on. Mention in the initial
- issue that you are planning to work on that bug or feature so that it can
- be assigned to you.
-
- 2. Follow the normal process of [forking][] the project, and setup a new
- branch to work in. It's important that each group of changes be done in
- separate branches in order to ensure that a pull request only includes the
- commits related to that bug or feature.
-
- 3. Do your best to have [well-formed commit messages][] for each change.
- This provides consistency throughout the project, and ensures that commit
- messages are able to be formatted properly by various git tools. If you
- are contributing to the codebase, make sure your code adheres to the
- appropriate [style guide][].
-
- 4. Finally, push the commits to your fork and submit a [pull request][].
-
-[forking]: https://help.github.com/articles/fork-a-repo
-[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
-[style guide]: https://github.com/google/physical-web/wiki/Coding-Style-Guides
-[pull request]: https://help.github.com/articles/creating-a-pull-request
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
deleted file mode 100644
index ee615d06..00000000
--- a/CONTRIBUTORS
+++ /dev/null
@@ -1,37 +0,0 @@
-CLA Signers
-----------------------------------------
-
-By default, anyone with an @google.com email address already has a CLA
-signed for them. Congratulations!
-
-alexyoung
-arkpog
-asagrawal
-daviddoran
-dermike
-devashishmamgain
-dinhviethoa
-don
-g-ortuno
-gbuesing
-grahamoregan
-gshaw
-ianobermiller
-jcrabtr
-jonathanstark
-keremgocen
-matthewsibigtroth
-morganchen12
-meriac
-MrNice
-Peeja
-Primigenus
-sandeepmistry
-schilit
-scottjenson
-ThunderKeg
-wen
-louaybassbouss
-devunwired
-ColeMurray
-edent
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d6456956..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- 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:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) 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
-
- (d) 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.
diff --git a/README.md b/README.md
deleted file mode 100755
index a171deea..00000000
--- a/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-#The Physical Web#
-
-
-The Physical Web is not shipping yet nor is it a Google product. This is an early-stage experimental project and we're developing it out in the open as we do all things related to the web. This should only be of interest to developers looking to test out this feature and provide us feedback.
-
-The Physical Web is an effort to extend the core superpower of the web - the URL - to everyday physical objects. Our core premise is that you should be able to walk up to any “smart” physical object (e.g. a vending machine, a poster, a toy, a bus stop, a rental car) and interact with it without first downloading an app. The user experience of smart objects should be much like links in a web browser: i.e., just tap and use.
-
-At its base, the Physical Web is a discovery service: a smart object broadcasts relevant URLs that any nearby device can receive. This simple capability can unlock exciting new ways to interact with the Web.
-
-
-
-[![Build Status](https://travis-ci.org/google/physical-web.svg?branch=master)](https://travis-ci.org/google/physical-web)
-
-## Why URLs?
-
-The URL is the fundamental building block of the web, giving remarkable flexibility of expression. It can be:
-
-* a web page with just a tiny paragraph of info
-* a fully interactive web page
-* a deep link into a native application
-
-##Why We're Doing This
-The number of smart objects is going to explode, both in our homes and in public spaces. Much like the web, there is going to be a long tail of interactivity for smart objects. But the overhead of installing an app for each one just doesn’t scale. We need a system that lets you walk up and use a device with just a tap. The Physical Web isn’t about replacing native apps; it’s about allowing interaction for the times when native apps just aren’t practical.
-
-##Open Design
-The Physical Web must be an open standard that everyone can use. This can’t be a product that is locked down by a single company. Like many web specifications, this is an open source design that is being released early so everyone can experiment and comment on it. There is much to discuss and add to this specification.
-
-##Contents
-* [Introduction/FAQ](http://github.com/google/physical-web/blob/master/documentation/introduction.md) - Most common questions
-* [Getting started guide](http://github.com/google/physical-web/blob/master/documentation/getting_started.md) - How you can try it out
-* [Technical overview](https://github.com/google/physical-web/blob/master/documentation/technical_overview.md)
-* [Eddystone](https://github.com/google/eddystone) - Repo with the specification for the beacons themselves
-* [mDNS support](https://github.com/google/physical-web/blob/master/documentation/mDNS_Support.md) - How to use mDNS instead of Bluetooth to broadcast URLs
-* [Branding guidelines](documentation/branding_guidelines.md)
-* [SSDP support](documentation/ssdp_support.md) - How to use SSDP instead of Bluetooth to broadcast URLs
-* Different Physical Web clients
- * [Android](android)
- * [IOS](ios)
- * [Node](nodejs) This is a minimal client meant for building utilities
diff --git a/android/LICENSE b/android/LICENSE
deleted file mode 100644
index d6456956..00000000
--- a/android/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- 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:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) 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
-
- (d) 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.
diff --git a/android/PhysicalWeb/.gitignore b/android/PhysicalWeb/.gitignore
deleted file mode 100644
index 76751cef..00000000
--- a/android/PhysicalWeb/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-# Keystore files for signing
-*.jks
-*.keystore
-
-*.iml
-.gradle
-.idea
-.DS_Store
-build
-local.properties
-signing.properties
diff --git a/android/PhysicalWeb/app/.gitignore b/android/PhysicalWeb/app/.gitignore
deleted file mode 100644
index 38c58757..00000000
--- a/android/PhysicalWeb/app/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/build
-.idea
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/build.gradle b/android/PhysicalWeb/app/build.gradle
deleted file mode 100644
index 70d13066..00000000
--- a/android/PhysicalWeb/app/build.gradle
+++ /dev/null
@@ -1,129 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'checkstyle'
-apply plugin: 'findbugs'
-apply plugin: 'pmd'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.2"
-
- defaultConfig {
- applicationId "physical_web.org.physicalweb"
- minSdkVersion 19
- targetSdkVersion 21
- versionCode 15
- versionName "0.1.856"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_7
- targetCompatibility JavaVersion.VERSION_1_7
- }
-
- if(new File("signing.properties").exists()) {
- Properties signingProperties = new Properties()
- signingProperties.load(new FileInputStream(new File('signing.properties')))
-
- signingConfigs {
- release {
- storeFile new File(signingProperties['storeFile'])
- storePassword signingProperties['storePassword']
- keyAlias signingProperties['keyAlias']
- keyPassword signingProperties['keyPassword']
- }
- }
-
- buildTypes {
- release {
- signingConfig signingConfigs.release
- }
- }
- }
-
- lintOptions {
- // We'll get to fixing the icon later
- disable 'IconLauncherShape', 'IconDensities', 'IconMissingDensityFolder'
- // Travis requires an older api at the moment
- disable 'OldTargetApi'
- }
-}
-
-repositories {
- flatDir {
- dirs 'libs'
- }
-}
-
-dependencies {
- compile(project(':libs')) {
- exclude group: 'org.json', module: 'json'
- }
-
- compile 'com.android.volley:volley@aar'
- compile 'org.uribeacon:uribeacon-library-release@aar'
-
- compile 'com.android.support:appcompat-v7:22.1.1'
-}
-
-task checkstyle(type: Checkstyle) {
- configProperties.checkstyleSuppressionsPath = new File(rootDir, "app/config/checkstyle/suppressions.xml").absolutePath
- source 'src'
- include '**/*.java'
- exclude '**/gen/**'
- classpath = files()
-}
-
-task findbugs(type: FindBugs, dependsOn: assembleDebug) {
- ignoreFailures = false
- effort = "max"
- reportLevel = "high"
- classes = files("${project.rootDir}/app/build/intermediates/classes")
- excludeFilter = file("${project.rootDir}/app/config/findbugs/exclude-filter.xml")
-
- source 'src'
- include '**/*.java'
- exclude '**/gen/**'
-
- reports {
- xml.enabled = false
- html.enabled = true
- xml {
- destination "$project.buildDir/reports/findbugs/findbugs.xml"
- }
- html {
- destination "$project.buildDir/reports/findbugs/findbugs.html"
- }
- }
-
- classpath = files()
-}
-
-task pmd(type: Pmd, dependsOn: assembleDebug) {
- ignoreFailures = false
- ruleSetFiles = files("${project.rootDir}/app/config/pmd/pmd-ruleset.xml")
- ruleSets = []
-
- source 'src'
- include '**/*.java'
- exclude '**/gen/**'
-
- reports {
- xml.enabled = false
- html.enabled = true
- xml {
- destination "$project.buildDir/reports/pmd/pmd.xml"
- }
- html {
- destination "$project.buildDir/reports/pmd/pmd.html"
- }
- }
-}
-
-check.dependsOn 'checkstyle', 'findbugs', 'lint', 'pmd'
diff --git a/android/PhysicalWeb/app/config/checkstyle/checkstyle.xml b/android/PhysicalWeb/app/config/checkstyle/checkstyle.xml
deleted file mode 100644
index 28f62d70..00000000
--- a/android/PhysicalWeb/app/config/checkstyle/checkstyle.xml
+++ /dev/null
@@ -1,338 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/config/checkstyle/suppressions.xml b/android/PhysicalWeb/app/config/checkstyle/suppressions.xml
deleted file mode 100644
index 0a255b21..00000000
--- a/android/PhysicalWeb/app/config/checkstyle/suppressions.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/config/findbugs/exclude-filter.xml b/android/PhysicalWeb/app/config/findbugs/exclude-filter.xml
deleted file mode 100644
index 1856a27b..00000000
--- a/android/PhysicalWeb/app/config/findbugs/exclude-filter.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/config/pmd/pmd-ruleset.xml b/android/PhysicalWeb/app/config/pmd/pmd-ruleset.xml
deleted file mode 100644
index 962c4268..00000000
--- a/android/PhysicalWeb/app/config/pmd/pmd-ruleset.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
- Custom ruleset for Android application
-
- .*/R.java
- .*/gen/.*
- .*/ble/UriBeacon.java
- .*/ble/Utils.java
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/libs/uribeacon-library-release.aar b/android/PhysicalWeb/app/libs/uribeacon-library-release.aar
deleted file mode 100644
index 434e874d..00000000
Binary files a/android/PhysicalWeb/app/libs/uribeacon-library-release.aar and /dev/null differ
diff --git a/android/PhysicalWeb/app/libs/volley.aar b/android/PhysicalWeb/app/libs/volley.aar
deleted file mode 100644
index d520b328..00000000
Binary files a/android/PhysicalWeb/app/libs/volley.aar and /dev/null differ
diff --git a/android/PhysicalWeb/app/manifest-merger-release-report.txt b/android/PhysicalWeb/app/manifest-merger-release-report.txt
deleted file mode 100644
index cf3b8f58..00000000
--- a/android/PhysicalWeb/app/manifest-merger-release-report.txt
+++ /dev/null
@@ -1,120 +0,0 @@
--- Merging decision tree log ---
-manifest
-ADDED from AndroidManifest.xml:2:1
- xmlns:android
- ADDED from AndroidManifest.xml:4:5
- package
- ADDED from AndroidManifest.xml:3:5
- INJECTED from AndroidManifest.xml:0:0
- INJECTED from AndroidManifest.xml:0:0
- android:versionName
- INJECTED from AndroidManifest.xml:0:0
- INJECTED from AndroidManifest.xml:0:0
- android:versionCode
- INJECTED from AndroidManifest.xml:0:0
- INJECTED from AndroidManifest.xml:0:0
-uses-feature#android.hardware.bluetooth_le
-ADDED from AndroidManifest.xml:6:5
- android:required
- ADDED from AndroidManifest.xml:8:9
- android:name
- ADDED from AndroidManifest.xml:7:9
-uses-permission#android.permission.INTERNET
-ADDED from AndroidManifest.xml:10:5
- android:name
- ADDED from AndroidManifest.xml:10:22
-uses-permission#android.permission.BLUETOOTH
-ADDED from AndroidManifest.xml:11:5
-MERGED from org.uribeacon:uribeacon-library-release::11:5
- android:name
- ADDED from AndroidManifest.xml:11:22
-uses-permission#android.permission.BLUETOOTH_ADMIN
-ADDED from AndroidManifest.xml:12:5
-MERGED from org.uribeacon:uribeacon-library-release::12:5
- android:name
- ADDED from AndroidManifest.xml:12:22
-uses-permission#android.permission.WAKE_LOCK
-ADDED from AndroidManifest.xml:14:5
- android:name
- ADDED from AndroidManifest.xml:14:22
-application
-ADDED from AndroidManifest.xml:16:5
-MERGED from com.android.volley:volley::9:5
-MERGED from com.android.support:appcompat-v7:21.0.3:16:5
-MERGED from com.android.support:support-v4:21.0.3:16:5
- android:label
- ADDED from AndroidManifest.xml:19:9
- android:allowBackup
- ADDED from AndroidManifest.xml:17:9
- android:icon
- ADDED from AndroidManifest.xml:18:9
- android:theme
- ADDED from AndroidManifest.xml:20:9
-activity#org.physical_web.physicalweb.MainActivity
-ADDED from AndroidManifest.xml:21:9
- android:windowSoftInputMode
- ADDED from AndroidManifest.xml:26:13
- android:screenOrientation
- ADDED from AndroidManifest.xml:25:13
- android:label
- ADDED from AndroidManifest.xml:23:13
- android:name
- ADDED from AndroidManifest.xml:22:13
- android:launchMode
- ADDED from AndroidManifest.xml:24:13
-intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER
-ADDED from AndroidManifest.xml:27:13
-action#android.intent.action.MAIN
-ADDED from AndroidManifest.xml:28:17
- android:name
- ADDED from AndroidManifest.xml:28:25
-category#android.intent.category.LAUNCHER
-ADDED from AndroidManifest.xml:30:17
- android:name
- ADDED from AndroidManifest.xml:30:27
-service#org.physical_web.physicalweb.UriBeaconDiscoveryService
-ADDED from AndroidManifest.xml:34:9
- android:exported
- ADDED from AndroidManifest.xml:37:13
- android:enabled
- ADDED from AndroidManifest.xml:36:13
- android:name
- ADDED from AndroidManifest.xml:35:13
-service#org.uribeacon.scan.compat.ScanWakefulService
-ADDED from AndroidManifest.xml:41:9
- android:exported
- ADDED from AndroidManifest.xml:43:13
- android:name
- ADDED from AndroidManifest.xml:42:13
-service#org.uribeacon.config.GattService
-ADDED from AndroidManifest.xml:45:9
- android:exported
- ADDED from AndroidManifest.xml:47:13
- android:name
- ADDED from AndroidManifest.xml:46:13
-receiver#org.uribeacon.scan.compat.ScanWakefulBroadcastReceiver
-ADDED from AndroidManifest.xml:51:9
- android:name
- ADDED from AndroidManifest.xml:51:19
-activity#org.physical_web.physicalweb.OobActivity
-ADDED from AndroidManifest.xml:54:9
- android:screenOrientation
- ADDED from AndroidManifest.xml:57:13
- android:label
- ADDED from AndroidManifest.xml:56:13
- android:theme
- ADDED from AndroidManifest.xml:58:13
- android:name
- ADDED from AndroidManifest.xml:55:13
-uses-sdk
-INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested
-MERGED from com.android.volley:volley::7:5
-MERGED from org.uribeacon:uribeacon-library-release::7:5
-MERGED from com.android.support:appcompat-v7:21.0.3:15:5
-MERGED from com.android.support:support-v4:21.0.3:15:5
- android:targetSdkVersion
- INJECTED from AndroidManifest.xml:0:0
- INJECTED from AndroidManifest.xml:0:0
- android:minSdkVersion
- INJECTED from AndroidManifest.xml:0:0
- INJECTED from AndroidManifest.xml:0:0
diff --git a/android/PhysicalWeb/app/proguard-rules.pro b/android/PhysicalWeb/app/proguard-rules.pro
deleted file mode 100644
index bb65c6fe..00000000
--- a/android/PhysicalWeb/app/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# 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/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/ApplicationTest.java b/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/ApplicationTest.java
deleted file mode 100644
index 65229fde..00000000
--- a/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.physical_web.physicalweb;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * Basic application test.
- */
-public class ApplicationTest extends ApplicationTestCase {
- public ApplicationTest() {
- super(Application.class);
- }
-}
diff --git a/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/BeaconDisplayListTest.java b/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/BeaconDisplayListTest.java
deleted file mode 100644
index bb06c859..00000000
--- a/android/PhysicalWeb/app/src/androidTest/java/org/physical_web/physicalweb/BeaconDisplayListTest.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.PwoMetadata.UrlMetadata;
-
-import android.test.AndroidTestCase;
-
-/**
- * Tests for BeaconDisplayList.
- */
-public class BeaconDisplayListTest extends AndroidTestCase {
- // sample data for constructing dummy beacon metadata
- private static final String URL = "http://physical-web.org";
- private static final String TITLE = "Physical Web";
- private static final String DEVICEADDRESS = "01:02:03:04:05:06";
- private static final String GROUPID = "groupid";
- private static final double RANK = 1000.0d;
- private static final long SCANMILLIS = 1000;
- private static final int RSSI = -100;
- private static final int TXPOWER = -20;
- private static final long PWSTRIPMILLIS = 2000;
- private static final String DESCRIPTION = "description";
- private static final String ICONURL = "iconUrl";
-
- // a second set of sample data for test cases that need two beacons
- private static final String URL2 = "http://example.com";
- private static final String TITLE2 = "Example Domain";
- private static final String DEVICEADDRESS2 = "0a:0b:0c:0d:0e:0f";
- private static final String GROUPID2 = "groupid2";
- private static final double RANK2 = 500.d;
-
- public BeaconDisplayListTest() {
- super();
- }
-
- private PwoMetadata createDummyDefaultPwo(String url) {
- return new PwoMetadata(url, SCANMILLIS);
- }
-
- private PwoMetadata createDummyBlePwo(String url) {
- PwoMetadata pwoMetadata = new PwoMetadata(url, SCANMILLIS);
- pwoMetadata.setBleMetadata(DEVICEADDRESS, RSSI, TXPOWER);
- return pwoMetadata;
- }
-
- private PwoMetadata createDummyBlePwoWithUrlMetadata(String url) {
- PwoMetadata pwoMetadata = new PwoMetadata(url, SCANMILLIS);
- pwoMetadata.setBleMetadata(DEVICEADDRESS, RSSI, TXPOWER);
- pwoMetadata.setUrlMetadata(createDummyUrlMetadata(url), PWSTRIPMILLIS);
- return pwoMetadata;
- }
-
- private UrlMetadata createDummyUrlMetadata(String url) {
- UrlMetadata urlMetadata = new UrlMetadata();
- urlMetadata.id = url;
- urlMetadata.siteUrl = url;
- urlMetadata.displayUrl = url;
- urlMetadata.title = TITLE;
- urlMetadata.description = DESCRIPTION;
- urlMetadata.iconUrl = ICONURL;
- urlMetadata.rank = RANK;
- urlMetadata.groupid = GROUPID;
-
- // no icon yet
- urlMetadata.icon = null;
-
- return urlMetadata;
- }
-
- public void testDisplayListAddAndRetrieveGenericPwo() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
- assertTrue(displayList.size() == 0);
- assertNull(displayList.getItem(0));
-
- PwoMetadata pwoMetadata = createDummyDefaultPwo(URL);
- boolean isPublic = pwoMetadata.isPublic;
-
- displayList.addItem(pwoMetadata);
- assertTrue(displayList.size() == 1);
-
- PwoMetadata retrievedPwoMetadata = displayList.getItem(0);
- assertNotNull(retrievedPwoMetadata);
- assertEquals(retrievedPwoMetadata.url, URL);
- assertEquals(retrievedPwoMetadata.scanMillis, SCANMILLIS);
- assertEquals(retrievedPwoMetadata.isPublic, isPublic);
- assertFalse(retrievedPwoMetadata.hasBleMetadata());
- assertFalse(retrievedPwoMetadata.hasUrlMetadata());
- }
-
- public void testDisplayListAddAndRetrieveBlePwo() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
- assertTrue(displayList.size() == 0);
- assertNull(displayList.getItem(0));
-
- PwoMetadata pwoMetadata = createDummyBlePwo(URL);
- boolean isPublic = pwoMetadata.isPublic;
-
- displayList.addItem(pwoMetadata);
- assertTrue(displayList.size() == 1);
-
- PwoMetadata retrievedPwoMetadata = displayList.getItem(0);
- assertNotNull(retrievedPwoMetadata);
- assertEquals(retrievedPwoMetadata.url, URL);
- assertEquals(retrievedPwoMetadata.scanMillis, SCANMILLIS);
- assertEquals(retrievedPwoMetadata.isPublic, isPublic);
- assertTrue(retrievedPwoMetadata.hasBleMetadata());
- assertEquals(retrievedPwoMetadata.bleMetadata.deviceAddress, DEVICEADDRESS);
- assertEquals(retrievedPwoMetadata.bleMetadata.rssi, RSSI);
- assertEquals(retrievedPwoMetadata.bleMetadata.txPower, TXPOWER);
- assertFalse(retrievedPwoMetadata.hasUrlMetadata());
- }
-
- public void testDisplayListAddAndRetrieveBlePwoWithUrlMetadata() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
- assertTrue(displayList.size() == 0);
- assertNull(displayList.getItem(0));
-
- PwoMetadata pwoMetadata = createDummyBlePwoWithUrlMetadata(URL);
- boolean isPublic = pwoMetadata.isPublic;
-
- displayList.addItem(pwoMetadata);
- assertTrue(displayList.size() == 1);
-
- PwoMetadata retrievedPwoMetadata = displayList.getItem(0);
- assertNotNull(retrievedPwoMetadata);
- assertEquals(retrievedPwoMetadata.url, URL);
- assertEquals(retrievedPwoMetadata.scanMillis, SCANMILLIS);
- assertEquals(retrievedPwoMetadata.isPublic, isPublic);
- assertTrue(retrievedPwoMetadata.hasBleMetadata());
- assertEquals(retrievedPwoMetadata.bleMetadata.deviceAddress, DEVICEADDRESS);
- assertEquals(retrievedPwoMetadata.bleMetadata.rssi, RSSI);
- assertEquals(retrievedPwoMetadata.bleMetadata.txPower, TXPOWER);
- assertTrue(retrievedPwoMetadata.hasUrlMetadata());
- assertEquals(retrievedPwoMetadata.urlMetadata.id, URL);
- assertEquals(retrievedPwoMetadata.urlMetadata.siteUrl, URL);
- assertEquals(retrievedPwoMetadata.urlMetadata.displayUrl, URL);
- assertEquals(retrievedPwoMetadata.urlMetadata.title, TITLE);
- assertEquals(retrievedPwoMetadata.urlMetadata.description, DESCRIPTION);
- assertEquals(retrievedPwoMetadata.urlMetadata.iconUrl, ICONURL);
- assertNull(retrievedPwoMetadata.urlMetadata.icon);
- assertEquals(retrievedPwoMetadata.urlMetadata.rank, RANK);
- assertEquals(retrievedPwoMetadata.urlMetadata.groupid, GROUPID);
- }
-
- public void testDisplayListAddPwoAndClear() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
- assertTrue(displayList.size() == 0);
- assertNull(displayList.getItem(0));
-
- displayList.addItem(new PwoMetadata(URL, SCANMILLIS));
- assertTrue(displayList.size() == 1);
- assertNotNull(displayList.getItem(0));
-
- displayList.clear();
- assertTrue(displayList.size() == 0);
- assertNull(displayList.getItem(0));
- }
-
- public void testDisplayListAddTwoDefaultPwos() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two PWOs with different URLs
- PwoMetadata deviceA = createDummyDefaultPwo(URL);
- PwoMetadata deviceB = createDummyDefaultPwo(URL2);
- displayList.addItem(deviceA);
- displayList.addItem(deviceB);
-
- // there should be two items
- assertTrue(displayList.size() == 2);
- }
-
- public void testDisplayListAddTwoBlePwos() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two BLE PWOs with different URLs
- PwoMetadata deviceA = createDummyBlePwo(URL);
- PwoMetadata deviceB = createDummyBlePwo(URL2);
- deviceB.setBleMetadata(DEVICEADDRESS2, RSSI, TXPOWER);
- displayList.addItem(deviceA);
- displayList.addItem(deviceB);
-
- // there should be two items
- assertTrue(displayList.size() == 2);
- }
-
- public void testDisplayListAddTwoBlePwosWithUrlMetadata() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two BLE PWOs with different URLs and URL metadata
- PwoMetadata deviceA = createDummyBlePwoWithUrlMetadata(URL);
- PwoMetadata deviceB = createDummyBlePwoWithUrlMetadata(URL2);
- deviceB.setBleMetadata(DEVICEADDRESS2, RSSI, TXPOWER);
- deviceB.urlMetadata.groupid = GROUPID2;
- displayList.addItem(deviceA);
- displayList.addItem(deviceB);
-
- // there should be two items
- assertTrue(displayList.size() == 2);
- }
-
- public void testDisplayListAddDuplicateDefaultPwo() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add the same PWO twice
- PwoMetadata device = createDummyDefaultPwo(URL);
- displayList.addItem(device);
- displayList.addItem(device);
-
- // there should only be one item
- assertTrue(displayList.size() == 1);
- }
-
- public void testDisplayListAddDuplicateBlePwo() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add the same BLE PWO twice
- PwoMetadata device = createDummyBlePwo(URL);
- displayList.addItem(device);
- displayList.addItem(device);
-
- // there should only be one item
- assertTrue(displayList.size() == 1);
- }
-
- public void testDisplayListAddDuplicateBlePwoWithUrlMetadata() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add the same BLE PWO (with URL metadata) twice
- PwoMetadata device = createDummyBlePwoWithUrlMetadata(URL);
- displayList.addItem(device);
- displayList.addItem(device);
-
- // there should only be one item
- assertTrue(displayList.size() == 1);
- }
-
- public void testDisplayListSameUrlDifferentDeviceAddress() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two BLE PWOs with the same URL that only differ by deviceAddress
- // (simulates two beacons advertising the same URL)
- PwoMetadata deviceA = createDummyBlePwo(URL);
- PwoMetadata deviceB = createDummyBlePwo(URL);
- deviceB.setBleMetadata(DEVICEADDRESS2, RSSI, TXPOWER);
-
- displayList.addItem(deviceA);
- displayList.addItem(deviceB);
-
- // both items should be displayed
- assertTrue(displayList.size() == 2);
- }
-
- public void testDisplayListAddDefaultPwoThenUrlMetadata() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add a PWO, then add the URL metadata for that PWO (simulate a PWS response)
- PwoMetadata device = createDummyDefaultPwo(URL);
- PwoMetadata deviceWithUrlMetadata = createDummyDefaultPwo(URL);
- deviceWithUrlMetadata.setUrlMetadata(createDummyUrlMetadata(URL), PWSTRIPMILLIS);
-
- displayList.addItem(device);
-
- // verify that the item has no URL metadata
- assertTrue(displayList.size() == 1);
- assertFalse(displayList.getItem(0).hasUrlMetadata());
-
- displayList.addItem(deviceWithUrlMetadata);
-
- // verify that we still only have one item and it now has URL metadata
- assertTrue(displayList.size() == 1);
- assertTrue(displayList.getItem(0).hasUrlMetadata());
- }
-
- public void testDisplayListAddBlePwoThenUrlMetadata() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add a PWO, then add the URL metadata for that PWO (simulate a PWS response)
- PwoMetadata device = createDummyBlePwo(URL);
- PwoMetadata deviceWithUrlMetadata = createDummyBlePwoWithUrlMetadata(URL);
-
- displayList.addItem(device);
-
- // verify that the item has no URL metadata
- assertTrue(displayList.size() == 1);
- assertFalse(displayList.getItem(0).hasUrlMetadata());
-
- displayList.addItem(deviceWithUrlMetadata);
-
- // verify that we still only have one item and it now has URL metadata
- assertTrue(displayList.size() == 1);
- assertTrue(displayList.getItem(0).hasUrlMetadata());
- }
-
- public void testDisplayListUpdateRank() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two PwoMetadatas that differ only by rank
- // (simulates a second PWS request for the same PWO at different range)
- PwoMetadata device = createDummyBlePwoWithUrlMetadata(URL);
- PwoMetadata deviceUpdated = createDummyBlePwoWithUrlMetadata(URL);
- deviceUpdated.urlMetadata.rank = RANK2;
-
- displayList.addItem(device);
-
- // verify original rank
- assertTrue(displayList.size() == 1);
- assertEquals(displayList.getItem(0).urlMetadata.rank, RANK);
-
- displayList.addItem(deviceUpdated);
-
- // verify updated rank
- assertTrue(displayList.size() == 1);
- assertEquals(displayList.getItem(0).urlMetadata.rank, RANK2);
- }
-
- public void testDisplayListUpdateTitleAndGroupid() throws Exception {
- BeaconDisplayList displayList = new BeaconDisplayList();
-
- // add two PwoMetadatas that differ only by title and groupid
- // (simulates updating the cached PWS site info after the page title is updated)
- PwoMetadata device = createDummyBlePwoWithUrlMetadata(URL);
- PwoMetadata deviceUpdated = createDummyBlePwoWithUrlMetadata(URL);
- deviceUpdated.urlMetadata.title = TITLE2;
- deviceUpdated.urlMetadata.groupid = GROUPID2;
-
- displayList.addItem(device);
-
- // verify original title and groupid
- assertTrue(displayList.size() == 1);
- assertEquals(displayList.getItem(0).urlMetadata.title, TITLE);
- assertEquals(displayList.getItem(0).urlMetadata.groupid, GROUPID);
-
- displayList.addItem(deviceUpdated);
-
- // verify updated title and groupid
- assertTrue(displayList.size() == 1);
- assertEquals(displayList.getItem(0).urlMetadata.title, TITLE2);
- assertEquals(displayList.getItem(0).urlMetadata.groupid, GROUPID2);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/AndroidManifest.xml b/android/PhysicalWeb/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 232877a4..00000000
--- a/android/PhysicalWeb/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AboutFragment.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AboutFragment.java
deleted file mode 100644
index 1e350ed0..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AboutFragment.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.annotation.SuppressLint;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.TextView;
-
-/**
- * The fragment that displays info about the app.
- */
-public class AboutFragment extends Fragment {
-
- @SuppressWarnings("WeakerAccess")
- public AboutFragment() {
- }
-
- public static AboutFragment newInstance() {
- return new AboutFragment();
- }
-
- private void initializeApplicationVersionText() {
- String versionString = getString(R.string.about_version_label) + " " + BuildConfig.VERSION_NAME;
- View view = getView();
- if (view != null) {
- TextView versionView = (TextView) view.findViewById(R.id.version);
- versionView.setText(versionString);
- }
- }
-
- @SuppressLint("SetJavaScriptEnabled")
- private void initializeWebView() {
- WebView webView = (WebView) getActivity().findViewById(R.id.about_webview);
- webView.getSettings().setJavaScriptEnabled(true);
- webView.setWebViewClient(new WebViewClient());
- webView.loadUrl(getString(R.string.url_getting_started));
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- setHasOptionsMenu(true);
- getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
- return inflater.inflate(R.layout.fragment_about, container, false);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- //noinspection ConstantConditions
- getActivity().getActionBar().setTitle(R.string.title_about);
- initializeWebView();
- initializeApplicationVersionText();
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.findItem(R.id.action_config).setVisible(false);
- menu.findItem(R.id.action_about).setVisible(false);
- }
-
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AutostartPwoDiscoveryServiceReceiver.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AutostartPwoDiscoveryServiceReceiver.java
deleted file mode 100644
index 491d966c..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/AutostartPwoDiscoveryServiceReceiver.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-
-/**
- * This receiver starts the UriBeaconDiscoveryService.
- */
-public class AutostartPwoDiscoveryServiceReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- // Make sure the user has opted in
- String preferencesKey = context.getString(R.string.main_prefs_key);
- SharedPreferences sharedPreferences =
- context.getSharedPreferences(preferencesKey, Context.MODE_PRIVATE);
- if (!sharedPreferences.getBoolean(context.getString(R.string.user_opted_in_flag), false)) {
- return;
- }
-
- Intent newIntent = new Intent(context, ScreenListenerService.class);
- context.startService(newIntent);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconConfigFragment.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconConfigFragment.java
deleted file mode 100644
index 4d1a4252..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconConfigFragment.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.app.Fragment;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.graphics.drawable.AnimationDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.os.SystemClock;
-import android.support.v4.content.res.ResourcesCompat;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.webkit.URLUtil;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import org.uribeacon.beacon.ConfigUriBeacon;
-import org.uribeacon.config.ProtocolV1;
-import org.uribeacon.config.ProtocolV2;
-import org.uribeacon.config.UriBeaconConfig;
-import org.uribeacon.scan.compat.ScanRecord;
-import org.uribeacon.scan.compat.ScanResult;
-import org.uribeacon.scan.util.RegionResolver;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This fragment is the ui that the user sees when
- * they have entered the app's beacon configuration mode.
- * This ui is where the user can view the current configuration
- * of a beacon (including it's address and url)
- * and also allows the user to enter a new url for that beacon.
- */
-
-public class BeaconConfigFragment extends Fragment implements TextView.OnEditorActionListener {
-
- private static final String TAG = "BeaconConfigFragment";
- private static final byte TX_POWER_DEFAULT = -22;
- private static final long SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(15);
- private static final ParcelUuid[] mScanFilterUuids =
- new ParcelUuid[]{ProtocolV2.CONFIG_SERVICE_UUID, ProtocolV1.CONFIG_SERVICE_UUID};
- private final BluetoothAdapter.LeScanCallback mLeScanCallback = new LeScanCallback();
- private BluetoothDevice mNearestDevice;
- private RegionResolver mRegionResolver;
- private EditText mEditCardUrl;
- private TextView mScanningStatus;
- private TextView mEditCardAddress;
- private LinearLayout mEditCard;
- private AnimationDrawable mScanningAnimation;
- private UriBeaconConfig mUriBeaconConfig;
- private boolean mIsScanRunning;
- private BluetoothAdapter mBluetoothAdapter;
- private Handler mHandler;
-
- // Run when the SCAN_TIME_MILLIS has elapsed.
- private Runnable mScanTimeout = new Runnable() {
- @Override
- public void run() {
- scanLeDevice(false);
- mScanningAnimation.stop();
- mScanningStatus.setCompoundDrawables(null, null, null, null);
- mScanningStatus.setText(R.string.config_no_beacons_found);
- }
- };
-
- public BeaconConfigFragment() {
- }
-
- public static BeaconConfigFragment newInstance() {
- return new BeaconConfigFragment();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mRegionResolver = new RegionResolver();
- mHandler = new Handler();
- initializeBluetooth();
- setHasOptionsMenu(true);
- getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- private void initializeBluetooth() {
- // Initializes a Bluetooth adapter. For API version 18 and above,
- // get a reference to BluetoothAdapter through BluetoothManager.
- final BluetoothManager bluetoothManager =
- (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
- mBluetoothAdapter = bluetoothManager.getAdapter();
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- // Inflate the layout for this fragment
- View view = inflater.inflate(R.layout.fragment_beacon_config, container, false);
-
- mEditCard = (LinearLayout) view.findViewById(R.id.edit_card);
-
- // Get handles to Status and Address views
- mEditCardAddress = (TextView) view.findViewById(R.id.edit_card_address);
-
- // Setup the URL Edit Text handler
- mEditCardUrl = (EditText) view.findViewById(R.id.edit_card_url);
- mEditCardUrl.setOnEditorActionListener(this);
-
- // Setup the animation
- mScanningStatus = (TextView) view.findViewById(R.id.textView_scanningStatus);
- mScanningAnimation = (AnimationDrawable) ResourcesCompat.getDrawable(
- getResources(), R.drawable.scanning_animation, null);
- mScanningStatus.setCompoundDrawablesWithIntrinsicBounds(null, mScanningAnimation, null, null);
-
- Button button = (Button) view.findViewById(R.id.edit_card_save);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- saveEditCardUrlToBeacon();
- }
- });
-
- return view;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getActivity().getActionBar().setTitle(R.string.title_edit_urls);
- mEditCardAddress.setText("");
- mEditCardUrl.setText("");
- mEditCard.setVisibility(View.INVISIBLE);
- mScanningStatus.setText(R.string.config_searching_for_beacons_text);
- mScanningAnimation.start();
- scanLeDevice(true);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mScanningAnimation.stop();
- scanLeDevice(false);
- PwsClient.getInstance(getActivity()).cancelAllRequests(TAG);
- if (mUriBeaconConfig != null) {
- mUriBeaconConfig.closeUriBeacon();
- }
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.findItem(R.id.action_config).setVisible(false);
- menu.findItem(R.id.action_about).setVisible(false);
- }
-
- /**
- * This is the class that listens for specific text entry events
- * (e.g. the DONE key)
- * on the edit text field that the user uses
- * to enter a new url for the configurable beacon
- */
- @Override
- public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
- // If the keyboard "DONE" button was pressed
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- // Hide the software keyboard
- hideSoftKeyboard();
- saveEditCardUrlToBeacon();
- return true;
- }
- return false;
- }
-
- /**
- * Check if the given url is a short url.
- *
- * @param url The url that will be tested to see if it is short
- * @return The value that indicates if the given url is short
- */
- private static boolean isShortUrl(String url) {
- return url.startsWith("http://goo.gl/") || url.startsWith("https://goo.gl/");
- }
-
- /**
- * Check if the given URL would fit in the UriBeacon uri field.
- * @param url URL to check
- * @return True if the URL length is within bounds
- */
- private static boolean hasValidUrlLength(String url) {
- int uriLength = ConfigUriBeacon.uriLength(url);
- return 0 <= uriLength && uriLength <= ConfigUriBeacon.MAX_URI_LENGTH;
- }
-
- /**
- * Check if the given URL only uses characters from the set defined in RFC 3986 section 2.
- * https://tools.ietf.org/html/rfc3986#section-2
- * @param url URL to check
- * @return True if the URL is RFC 3986 compliant
- */
- private static boolean isAsciiUrl(String url) {
- boolean isCompliant = false;
- try {
- URI uri = new URI(url);
- String urlString = uri.toASCIIString();
- isCompliant = url.equals(urlString);
- } catch (URISyntaxException e) {
- // bad url
- }
- return isCompliant;
- }
-
- public void onBeaconConfigReadUrlComplete(ConfigUriBeacon uriBeacon, int status) {
- if (status != BluetoothGatt.GATT_SUCCESS) {
- Log.e(TAG, "onUriBeaconRead - error " + status);
- return;
- }
-
- if (uriBeacon == null) {
- Toast.makeText(getActivity(), R.string.config_url_read_error, Toast.LENGTH_SHORT).show();
- setEditCardUrl("");
- return;
- }
-
- final String url = uriBeacon.getUriString();
- Log.d(TAG, "onReadUrlComplete" + " url: " + url);
- if (!isShortUrl(url)) {
- setEditCardUrl(url);
- }
-
- // Create the callback object to set the url
- PwsClient.ResolveScanCallback resolveScanCallback = new PwsClient.ResolveScanCallback() {
- @Override
- public void onUrlMetadataReceived(PwoMetadata pwoMetadata){
- setEditCardUrl(pwoMetadata.urlMetadata.siteUrl);
- }
-
- @Override
- public void onUrlMetadataAbsent(PwoMetadata pwoMetadata){
- setEditCardUrl(url);
- }
- };
- PwoMetadata pwoMetadata = new PwoMetadata(url, 0);
- PwsClient.getInstance(getActivity()).findUrlMetadata(pwoMetadata, resolveScanCallback, TAG);
- }
-
- public void onBeaconConfigWriteUrlComplete(final int status) {
- // Detach this fragment from its activity
- getFragmentManager().popBackStack();
- // Show a toast to the user to let them know if the Url was written or error occurred.
- int msgId = (status == BluetoothGatt.GATT_SUCCESS)
- ? R.string.config_url_saved : R.string.config_url_error;
- Toast.makeText(getActivity(), msgId, Toast.LENGTH_SHORT).show();
- }
-
- @SuppressWarnings("deprecation")
- private void scanLeDevice(final boolean enable) {
- if (mIsScanRunning != enable) {
- mIsScanRunning = enable;
- // If we should start scanning
- if (enable) {
- // Stops scanning after the predefined scan time has elapsed.
- mHandler.postDelayed(mScanTimeout, SCAN_TIME_MILLIS);
- // Start the scan
- mBluetoothAdapter.startLeScan(mLeScanCallback);
- // If we should stop scanning
- } else {
- // Cancel the scan timeout callback if still active or else it may fire later.
- mHandler.removeCallbacks(mScanTimeout);
- // Stop the scan
- mBluetoothAdapter.stopLeScan(mLeScanCallback);
- }
- }
- }
-
- private ParcelUuid leScanMatches(ScanRecord scanRecord) {
- List services = scanRecord.getServiceUuids();
- if (services != null) {
- for (ParcelUuid uuid : mScanFilterUuids) {
- if (services.contains(uuid)) {
- return uuid;
- }
- }
- }
- return null;
- }
-
- private void handleFoundDevice(final ScanResult scanResult, ParcelUuid filteredUuid) {
- final String address = scanResult.getDevice().getAddress();
- int rxPower = scanResult.getRssi();
- int txPower = scanResult.getScanRecord().getTxPowerLevel();
- mRegionResolver.onUpdate(address, rxPower, txPower);
- final String nearestAddress = mRegionResolver.getNearestAddress();
- // TODO(?): re-implement nearest address methodology once ranging bug is fixed
- // When the current sighting comes from the nearest device...
- // Remove the `|| true`
- if (address.equals(nearestAddress) || true) {
- // Stopping the scan in this thread is important for responsiveness
- scanLeDevice(false);
- mUriBeaconConfig = new UriBeaconConfig(getActivity(), new UriBeaconConfigCallback(),
- filteredUuid);
- mNearestDevice = scanResult.getDevice();
- mUriBeaconConfig.connectUriBeacon(mNearestDevice);
- } else {
- mNearestDevice = null;
- }
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (mNearestDevice != null) {
- // Remove animation
- mScanningStatus.setCompoundDrawables(null, null, null, null);
- mScanningStatus.setText(R.string.config_found_near_beacon);
- mEditCardAddress.setText(nearestAddress);
- } else {
- mScanningStatus.setText(R.string.config_found_far_beacon);
- }
- }
- });
- }
-
- /**
- * Hide the software keyboard.
- */
- private void hideSoftKeyboard() {
- InputMethodManager imm =
- (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(mEditCardUrl.getWindowToken(), 0);
- }
-
- /**
- * Show the card that displays the address and url of the currently-being-configured beacon.
- */
- private void showConfigurableBeaconCard() {
- mEditCard.setVisibility(View.VISIBLE);
- Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.fade_in_and_slide_up);
- mEditCard.startAnimation(animation);
- }
-
- public void setEditCardUrl(String url) {
- // Update the url edit text field with the given url
- mEditCardUrl.setText(url);
- // Show the beacon configuration card
- showConfigurableBeaconCard();
- }
-
- /**
- * Write a beacon url to the beacon.
- */
- private void setUriBeaconUrl(String url) {
- try {
- // Note: setting txPower here only really updates txPower for v1 beacons
- ConfigUriBeacon configUriBeacon = new ConfigUriBeacon.Builder()
- .txPowerLevel(TX_POWER_DEFAULT)
- .uriString(url)
- .build();
- mUriBeaconConfig.writeUriBeacon(configUriBeacon);
- } catch (URISyntaxException e) {
- Log.e(TAG, "setUriBeaconUrl error: " + e);
- Toast.makeText(getActivity(), R.string.config_url_error, Toast.LENGTH_SHORT).show();
- }
- }
-
- /**
- * This is called when the user taps the write-to-beacon button.
- */
- private void saveEditCardUrlToBeacon() {
- // Update the status text
- mScanningStatus.setText(R.string.config_writing_to_beacon_text);
- // Remove the focus from the url edit text field
- mEditCard.clearFocus();
- // Get the current text in the url edit text field.
- String url = mEditCardUrl.getText().toString();
- // Ensure an http prefix exists in the url
- if (!URLUtil.isNetworkUrl(url)) {
- url = "http://" + url;
- }
- // Create the callback object to set the url
- PwsClient.ShortenUrlCallback urlSetter = new PwsClient.ShortenUrlCallback() {
- @Override
- public void onUrlShortened(String newUrl) {
- setUriBeaconUrl(newUrl);
- }
- @Override
- public void onError(String oldUrl) {
- // Avoid showing a "URL too long" error if we shortened due to non-ASCII chars in url
- int errorMessageStringId = R.string.url_shortening_error;
- if (hasValidUrlLength(oldUrl) && !isAsciiUrl(oldUrl)) {
- errorMessageStringId = R.string.url_charset_error;
- }
- Toast.makeText(getActivity(), errorMessageStringId, Toast.LENGTH_SHORT).show();
- }
- };
-
- if (hasValidUrlLength(url) && isAsciiUrl(url)) {
- // Set the url if we can
- setUriBeaconUrl(url);
- } else {
- // Shorten the url if necessary
- PwsClient.getInstance(getActivity()).shortenUrl(url, urlSetter, TAG);
- }
- }
-
- /**
- * Callback for LE scan results.
- */
- private class LeScanCallback implements BluetoothAdapter.LeScanCallback {
- @Override
- public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanBytes) {
- ScanRecord scanRecord = ScanRecord.parseFromBytes(scanBytes);
- ParcelUuid filteredUuid = leScanMatches(scanRecord);
- if (filteredUuid != null) {
- final ScanResult scanResult = new ScanResult(device, scanRecord, rssi,
- SystemClock.elapsedRealtimeNanos());
- handleFoundDevice(scanResult, filteredUuid);
- }
- }
- }
-
- class UriBeaconConfigCallback implements UriBeaconConfig.UriBeaconCallback {
-
- @Override
- public void onUriBeaconRead(ConfigUriBeacon configUriBeacon, int status) {
- onBeaconConfigReadUrlComplete(configUriBeacon, status);
- }
-
- @Override
- public void onUriBeaconWrite(int status) {
- onBeaconConfigWriteUrlComplete(status);
- }
- }
-
-}
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconDisplayList.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconDisplayList.java
deleted file mode 100644
index 2df2da31..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BeaconDisplayList.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
-
-class BeaconDisplayList {
-
- /**
- * An entry in the beacon display list.
- */
- private interface BeaconDisplayItem {
- PwoMetadata top();
- }
-
- /**
- * Container for all metadata related to a particular physical web object (PWO) device.
- */
- private class PwoDevice implements BeaconDisplayItem {
- private PwoMetadata mPwoMetadata;
-
- PwoDevice() {
- mPwoMetadata = null;
- }
-
- /**
- * Append new metadata for this PWO.
- * @param pwoMetadata Updated metadata for this PWO
- */
- public void addItem(PwoMetadata pwoMetadata) {
- // TODO(mattreynolds): track PWO history (for now, just keep the most recent)
- mPwoMetadata = pwoMetadata;
- }
-
- /**
- * Returns the most relevant metadata for this PWO.
- * @return PwoMetadata
- */
- @Override
- public PwoMetadata top() {
- return mPwoMetadata;
- }
- }
-
- /**
- * Container for all PWOs in the same group.
- */
- private class PwoGroup implements BeaconDisplayItem {
- private HashMap mPwoDevices;
-
- PwoGroup() {
- mPwoDevices = new HashMap<>();
- }
-
- /**
- * Append new metadata for a PWO in this group, creating a new PwoDevice if needed.
- * @param pwoMetadata New PwoMetadata
- */
- public void addItem(PwoMetadata pwoMetadata) {
- String groupedPwoId = getPwoId(pwoMetadata);
-
- PwoDevice pwoDevice = mPwoDevices.get(groupedPwoId);
- boolean isNewDevice = false;
- if (pwoDevice == null) {
- pwoDevice = new PwoDevice();
- isNewDevice = true;
- }
- pwoDevice.addItem(pwoMetadata);
-
- if (isNewDevice) {
- mPwoDevices.put(groupedPwoId, pwoDevice);
- }
- }
-
- /**
- * Remove a PWO from this group.
- * @param pwoMetadata The PwoDevice to remove
- */
- public void removeItem(PwoMetadata pwoMetadata) {
- mPwoDevices.remove(getPwoId(pwoMetadata));
- }
-
- /**
- * Return true if the specified PWO is in this group.
- * @param pwoMetadata The PwoDevice
- * @return boolean true if present
- */
- public boolean contains(PwoMetadata pwoMetadata) {
- return mPwoDevices.containsKey(getPwoId(pwoMetadata));
- }
-
- /**
- * Promote an existing ungrouped PWO device into this PWO group.
- * @param pwoDevice The PwoDevice to promote
- */
- public void promotePwo(PwoDevice pwoDevice) {
- PwoMetadata topPwoMetadata = pwoDevice.top();
- String groupedPwoId = getPwoId(topPwoMetadata);
-
- // only promote if the PWO doesn't already exist
- if (groupedPwoId != null && !mPwoDevices.containsKey(groupedPwoId)) {
- mPwoDevices.put(groupedPwoId, pwoDevice);
- }
- }
-
- /**
- * Returns metadata for the top PWO device in this group.
- * @return PwoMetadata
- */
- @Override
- public PwoMetadata top() {
- PriorityQueue groupedPwos = new PriorityQueue<>();
- for (PwoDevice pwoDevice : mPwoDevices.values()) {
- groupedPwos.add(pwoDevice.top());
- }
- return groupedPwos.peek();
- }
- }
-
- private List mDisplayList;
- private HashMap mPwoGroups;
- private HashMap mUngroupedPwoDevices;
-
- BeaconDisplayList() {
- mDisplayList = new ArrayList<>();
- mPwoGroups = new HashMap<>();
- mUngroupedPwoDevices = new HashMap<>();
- }
-
- /**
- * Append new PWO metadata and update the list of displayed beacons.
- * @param pwoMetadata New PwoMetadata
- */
- public void addItem(PwoMetadata pwoMetadata) {
- // PWOs in the same group are intended to represent the same result, but with minimally
- // different URLs (e.g. a differing hash variable). To avoid confusing users with many
- // similar results, we instead display one grouped result that links to the URL of the
- // top-ranked PWO in the group.
-
- String groupid = pwoMetadata.getGroupid();
- if (groupid == null) {
- updateUngroupedPwo(pwoMetadata);
- } else {
- // Check if this metadata matches an ungrouped PWO. If so, promote it to a grouped PWO.
- String ungroupedPwoId = getPwoId(pwoMetadata);
- PwoDevice ungroupedPwoDevice = mUngroupedPwoDevices.get(ungroupedPwoId);
- if (ungroupedPwoDevice != null) {
- promoteUngroupedPwo(groupid, ungroupedPwoDevice);
- }
-
- updateGroupedPwo(groupid, pwoMetadata);
- }
- }
-
- /**
- * Return metadata for the PWO at the specified index in the display list.
- * @param i Index into display list
- * @return PwoMetadata
- */
- public PwoMetadata getItem(int i) {
- if (i >= 0 && i < mDisplayList.size()) {
- return mDisplayList.get(i).top();
- }
-
- return null;
- }
-
- /**
- * Return true if any contained pwo has the specified URL.
- * @param url The URL
- * @return boolean true if present
- */
- public boolean containsUrl(String url) {
- for (BeaconDisplayItem beaconDisplayItem : mDisplayList) {
- if (beaconDisplayItem.top().url.equals(url)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Return the display list item count.
- * @return Count of displayed items
- */
- public int size() {
- return mDisplayList.size();
- }
-
- /**
- * Clear the display list and forget all cached metadata.
- */
- public void clear() {
- mDisplayList.clear();
- mPwoGroups.clear();
- mUngroupedPwoDevices.clear();
- }
-
- /**
- * Update metadata for a grouped PWO, creating a new PWO group if needed.
- * @param groupid String identifier for the PWO group
- * @param pwoMetadata The grouped PWO metadata
- */
- private void updateGroupedPwo(String groupid, PwoMetadata pwoMetadata) {
- // Is this device already in a group? If so, remove it first
- PwoGroup previousPwoGroup = null;
- String previousPwoGroupid = null;
- for (Map.Entry entry : mPwoGroups.entrySet()) {
- PwoGroup candidateGroup = entry.getValue();
- if (candidateGroup.contains(pwoMetadata)) {
- previousPwoGroup = candidateGroup;
- previousPwoGroupid = entry.getKey();
- break;
- }
- }
- if (previousPwoGroup != null && !groupid.equals(previousPwoGroupid)) {
- // Remove the device from the group
- previousPwoGroup.removeItem(pwoMetadata);
-
- // Also remove the group if it's empty
- if (previousPwoGroup.top() == null) {
- mDisplayList.remove(previousPwoGroup);
- mPwoGroups.remove(previousPwoGroupid);
- }
- }
-
- PwoGroup pwoGroup = mPwoGroups.get(groupid);
-
- boolean isNewGroup = false;
- if (pwoGroup == null) {
- pwoGroup = new PwoGroup();
- mPwoGroups.put(groupid, pwoGroup);
- isNewGroup = true;
- }
- pwoGroup.addItem(pwoMetadata);
-
- // Avoid adding items to the display list until they are populated with PwoMetadata
- if (isNewGroup) {
- mDisplayList.add(pwoGroup);
- }
- }
-
- /**
- * Update metadata for an ungrouped PWO, creating a new PWO device if needed.
- * @param pwoMetadata The ungrouped PWO metadata
- */
- private void updateUngroupedPwo(PwoMetadata pwoMetadata) {
- String ungroupedPwoId = getPwoId(pwoMetadata);
- PwoDevice ungroupedPwoDevice = mUngroupedPwoDevices.get(ungroupedPwoId);
-
- boolean isNewDevice = false;
- if (ungroupedPwoDevice == null) {
- ungroupedPwoDevice = new PwoDevice();
- isNewDevice = true;
- }
- ungroupedPwoDevice.addItem(pwoMetadata);
-
- // Avoid adding items to the display list until they are populated with PwoMetadata
- if (isNewDevice) {
- mUngroupedPwoDevices.put(ungroupedPwoId, ungroupedPwoDevice);
- mDisplayList.add(ungroupedPwoDevice);
- }
- }
-
- /**
- * Create a new PWO group and promote an ungrouped PWO as the initial PWO in the group.
- * @param groupid String identifier for the new PWO group
- * @param ungroupedPwo The PWO to promote
- */
- private void promoteUngroupedPwo(String groupid, PwoDevice ungroupedPwo) {
- String ungroupedPwoId = getPwoId(ungroupedPwo.top());
-
- mDisplayList.remove(ungroupedPwo);
- mUngroupedPwoDevices.remove(ungroupedPwoId);
-
- PwoGroup pwoGroup = new PwoGroup();
- pwoGroup.promotePwo(ungroupedPwo);
- mPwoGroups.put(groupid, pwoGroup);
- mDisplayList.add(pwoGroup);
- }
-
- private static String getPwoId(PwoMetadata pwoMetadata) {
- // Prefer an id that uniquely identifies the device, but use URL as a fallback for PWOs
- // that do not have BLE metadata or otherwise don't support a unique device id.
- String deviceAddress = pwoMetadata.getDeviceAddress();
- if (deviceAddress != null && !deviceAddress.equals("")) {
- return "ble:" + deviceAddress;
- }
- return "url:" + pwoMetadata.url;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BlePwoDiscoverer.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BlePwoDiscoverer.java
deleted file mode 100644
index 0f9b840c..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/BlePwoDiscoverer.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.ble.ScanRecord;
-import org.physical_web.physicalweb.ble.UriBeacon;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.webkit.URLUtil;
-
-import java.util.List;
-
-class BlePwoDiscoverer extends PwoDiscoverer implements BluetoothAdapter.LeScanCallback {
- private static final String TAG = "BlePwoDiscoverer";
- private static final ParcelUuid URIBEACON_SERVICE_UUID =
- ParcelUuid.fromString("0000FED8-0000-1000-8000-00805F9B34FB");
- private static final ParcelUuid EDDYSTONE_URL_SERVICE_UUID =
- ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
- private BluetoothAdapter mBluetoothAdapter;
- private Parcelable[] mScanFilterUuids;
- private boolean isRunning;
-
- public BlePwoDiscoverer(Context context) {
- final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(
- Context.BLUETOOTH_SERVICE);
- mBluetoothAdapter = bluetoothManager.getAdapter();
- mScanFilterUuids = new ParcelUuid[]{URIBEACON_SERVICE_UUID, EDDYSTONE_URL_SERVICE_UUID};
- }
-
- private boolean leScanMatches(ScanRecord scanRecord) {
- if (mScanFilterUuids == null) {
- return true;
- }
- List services = scanRecord.getServiceUuids();
- if (services != null) {
- for (Parcelable uuid : mScanFilterUuids) {
- if (services.contains(uuid)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanBytes) {
- if (!leScanMatches(ScanRecord.parseFromBytes(scanBytes))) {
- return;
- }
-
- UriBeacon uriBeacon = UriBeacon.parseFromBytes(scanBytes);
- if (uriBeacon == null) {
- return;
- }
-
- String url = uriBeacon.getUriString();
- if (!URLUtil.isNetworkUrl(url)) {
- return;
- }
-
- PwoMetadata pwoMetadata = createPwoMetadata(url);
- pwoMetadata.setBleMetadata(device.getAddress(), rssi, uriBeacon.getTxPowerLevel());
- pwoMetadata.bleMetadata.updateRegionInfo();
- reportPwo(pwoMetadata);
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public synchronized void startScanImpl() {
- mBluetoothAdapter.startLeScan(this);
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public synchronized void stopScanImpl() {
- mBluetoothAdapter.stopLeScan(this);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MainActivity.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MainActivity.java
deleted file mode 100644
index 13d045d9..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MainActivity.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.widget.Toast;
-
-/**
- * The main entry point for the app.
- */
-
-public class MainActivity extends Activity {
- private static final int REQUEST_ENABLE_BT = 0;
- private static final String NEARBY_BEACONS_FRAGMENT_TAG = "NearbyBeaconsFragmentTag";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
-
- /**
- * Ensures Bluetooth is available on the beacon and it is enabled. If not,
- * displays a dialog requesting user permission to enable Bluetooth.
- */
- private void ensureBluetoothIsEnabled(BluetoothAdapter bluetoothAdapter) {
- if (!bluetoothAdapter.isEnabled()) {
- Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- /**
- * Called when a menu item is tapped.
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- // If the configuration menu item was selected
- case R.id.action_config:
- showBeaconConfigFragment();
- return true;
- // If the about menu item was selected
- case R.id.action_about:
- showAboutFragment();
- return true;
- // If the action bar up button was pressed
- case android.R.id.home:
- getFragmentManager().popBackStack();
- getActionBar().setDisplayHomeAsUpEnabled(false);
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- BluetoothManager btManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
- BluetoothAdapter btAdapter = btManager != null ? btManager.getAdapter() : null;
- if (btAdapter == null) {
- Toast.makeText(getApplicationContext(),
- R.string.error_bluetooth_support, Toast.LENGTH_LONG).show();
- finish();
- return;
- }
- if (checkIfUserHasOptedIn()) {
- ensureBluetoothIsEnabled(btAdapter);
- showNearbyBeaconsFragment();
- Intent intent = new Intent(this, ScreenListenerService.class);
- startService(intent);
- } else {
- // Show the oob activity
- Intent intent = new Intent(this, OobActivity.class);
- startActivity(intent);
- }
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- }
-
- /**
- * Show the fragment scanning nearby UriBeacons.
- */
- private void showNearbyBeaconsFragment() {
- // Look for an instance of the nearby beacons fragment
- Fragment nearbyBeaconsFragment =
- getFragmentManager().findFragmentByTag(NEARBY_BEACONS_FRAGMENT_TAG);
- // If the fragment does not exist
- if (nearbyBeaconsFragment == null) {
- // Create the fragment
- getFragmentManager().beginTransaction()
- .replace(R.id.main_activity_container, NearbyBeaconsFragment.newInstance(),
- NEARBY_BEACONS_FRAGMENT_TAG)
- .commit();
- // If the fragment does exist
- } else {
- // If the fragment is not currently visible
- if (!nearbyBeaconsFragment.isVisible()) {
- // Assume another fragment is visible, so pop that fragment off the stack
- getFragmentManager().popBackStack();
- }
- }
- }
-
- /**
- * Show the fragment configuring a beacon.
- */
- private void showBeaconConfigFragment() {
- BeaconConfigFragment beaconConfigFragment = BeaconConfigFragment.newInstance();
- getFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.fade_in_and_slide_up_fragment, R.anim.fade_out_fragment,
- R.anim.fade_in_activity, R.anim.fade_out_fragment)
- .replace(R.id.main_activity_container, beaconConfigFragment)
- .addToBackStack(null)
- .commit();
- }
-
- /**
- * Show the fragment displaying information about this application.
- */
- private void showAboutFragment() {
- AboutFragment aboutFragment = AboutFragment.newInstance();
- getFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.fade_in_and_slide_up_fragment, R.anim.fade_out_fragment,
- R.anim.fade_in_activity, R.anim.fade_out_fragment)
- .replace(R.id.main_activity_container, aboutFragment)
- .addToBackStack(null)
- .commit();
- }
-
- private boolean checkIfUserHasOptedIn() {
- String preferencesKey = getString(R.string.main_prefs_key);
- SharedPreferences sharedPreferences = getSharedPreferences(preferencesKey,
- Context.MODE_PRIVATE);
- return sharedPreferences.getBoolean(getString(R.string.user_opted_in_flag), false);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MdnsPwoDiscoverer.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MdnsPwoDiscoverer.java
deleted file mode 100644
index 0c4a6ef8..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MdnsPwoDiscoverer.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.util.Log;
-import android.webkit.URLUtil;
-
-class MdnsPwoDiscoverer extends PwoDiscoverer {
-
- private static final String TAG = "MdnsPwoDiscoverer";
- NsdManager.DiscoveryListener mDiscoveryListener = new NsdManager.DiscoveryListener() {
-
- @Override
- public void onDiscoveryStarted(String regType) {
- Log.d(TAG, "Service discovery started");
- mState = State.STARTED;
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo service) {
- Log.d(TAG, "Service discovery success" + service);
- String name = service.getServiceName();
- if (URLUtil.isNetworkUrl(name)) {
- PwoMetadata pwoMetadata = createPwoMetadata(name);
- pwoMetadata.isPublic = false;
- reportPwo(pwoMetadata);
- }
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo service) {
- Log.e(TAG, "service lost" + service);
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- Log.i(TAG, "Discovery stopped: " + serviceType);
- mState = State.STOPPED;
- if (toRestart) {
- toRestart = false;
- startScan();
- }
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- mNsdManager.stopServiceDiscovery(this);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- mNsdManager.stopServiceDiscovery(this);
- }
- };
- private static final String MDNS_SERVICE_TYPE = "_http._tcp.";
- private NsdManager mNsdManager;
- private enum State {
- STOPPED,
- WAITING,
- STARTED,
- }
- private State mState;
- private boolean toRestart;
-
- public MdnsPwoDiscoverer(Context context) {
- mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
- mState = State.STOPPED;
- toRestart = false;
- }
-
- @Override
- public synchronized void startScanImpl() {
- if (mState != State.STOPPED) {
- return;
- }
- mState = State.WAITING;
- mNsdManager.discoverServices(MDNS_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
- }
-
- @Override
- public synchronized void stopScanImpl() {
- if (mState != State.STARTED) {
- return;
- }
- mState = State.WAITING;
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- }
-
- @Override
- public synchronized void restartScan() {
- toRestart = true;
- stopScan();
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/NearbyBeaconsFragment.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/NearbyBeaconsFragment.java
deleted file mode 100644
index 15956d79..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/NearbyBeaconsFragment.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.PwoMetadata.BleMetadata;
-import org.physical_web.physicalweb.PwoMetadata.UrlMetadata;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ObjectAnimator;
-import android.annotation.SuppressLint;
-import android.app.ListFragment;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.drawable.AnimationDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class shows the ui list for all
- * detected nearby beacons.
- * It also listens for tap events
- * on items within the list.
- * Tapped list items then launch
- * the browser and point that browser
- * to the given list items url.
- */
-public class NearbyBeaconsFragment extends ListFragment
- implements PwoDiscoveryService.PwoResponseCallback,
- SwipeRefreshWidget.OnRefreshListener {
-
- private static final String TAG = "NearbyBeaconsFragment";
- private static final long FIRST_SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(2);
- private static final long SECOND_SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5);
- private static final long THIRD_SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(10);
- private List mUrlQueue;
- private HashMap mUrlToPwoMetadata = null;
- private TextView mScanningAnimationTextView;
- private AnimationDrawable mScanningAnimationDrawable;
- private Handler mHandler;
- private NearbyBeaconsAdapter mNearbyDeviceAdapter;
- private SwipeRefreshWidget mSwipeRefreshWidget;
- private boolean mDebugViewEnabled = false;
- private boolean mSecondScanComplete;
-
- // The display of gathered urls happens as follows
- // 0. Begin scan
- // 1. Sort and show all urls (mFirstScanTimeout)
- // 2. Sort and show all new urls beneath the first set (mSecondScanTimeout)
- // 3. Show each new url at bottom of list as it comes in
- // 4. Stop scanning (mThirdScanTimeout)
-
- // Run when the FIRST_SCAN_MILLIS has elapsed.
- private Runnable mFirstScanTimeout = new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "running first scan timeout");
- if (!mUrlQueue.isEmpty()) {
- emptyUrlQueue();
- showListView();
- }
- }
- };
-
- // Run when the SECOND_SCAN_MILLIS has elapsed.
- private Runnable mSecondScanTimeout = new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "running second scan timeout");
- emptyUrlQueue();
- showListView();
- mSecondScanComplete = true;
- }
- };
-
- // Run when the THIRD_SCAN_MILLIS has elapsed.
- private Runnable mThirdScanTimeout = new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "running third scan timeout");
- mDiscoveryServiceConnection.disconnect();
- }
- };
-
- private AdapterView.OnItemLongClickListener mAdapterViewItemLongClickListener =
- new AdapterView.OnItemLongClickListener() {
- public boolean onItemLongClick(AdapterView> av, View v, int position, long id) {
- mDebugViewEnabled = !mDebugViewEnabled;
- mNearbyDeviceAdapter.notifyDataSetChanged();
- return true;
- }
- };
-
- /**
- * The connection to the service that discovers urls.
- */
- private class DiscoveryServiceConnection implements ServiceConnection {
- private PwoDiscoveryService mDiscoveryService;
- private boolean mRequestCachedPwos;
-
- @Override
- public synchronized void onServiceConnected(ComponentName className, IBinder service) {
- // Get the service
- PwoDiscoveryService.LocalBinder localBinder = (PwoDiscoveryService.LocalBinder) service;
- mDiscoveryService = localBinder.getServiceInstance();
-
- // Start the scanning display
- mDiscoveryService.addCallback(NearbyBeaconsFragment.this);
- if (!mRequestCachedPwos) {
- mDiscoveryService.restartScan();
- }
- mUrlToPwoMetadata = mDiscoveryService.getUrlToPwoMetadataMap();
- startScanningDisplay(mDiscoveryService.getScanStartTime(), mDiscoveryService.hasResults());
- }
-
- @Override
- public synchronized void onServiceDisconnected(ComponentName className) {
- // onServiceDisconnected gets called when the connection is unintentionally disconnected,
- // which should never happen for us since this is a local service
- mDiscoveryService = null;
- }
-
- public synchronized void connect(boolean requestCachedPwos) {
- if (mDiscoveryService != null) {
- return;
- }
-
- mRequestCachedPwos = requestCachedPwos;
- Intent intent = new Intent(getActivity(), PwoDiscoveryService.class);
- getActivity().startService(intent);
- getActivity().bindService(intent, this, Context.BIND_AUTO_CREATE);
- }
-
- public synchronized void disconnect() {
- if (mDiscoveryService == null) {
- return;
- }
-
- mDiscoveryService.removeCallback(NearbyBeaconsFragment.this);
- mDiscoveryService = null;
- getActivity().unbindService(this);
- stopScanningDisplay();
- }
- }
- private DiscoveryServiceConnection mDiscoveryServiceConnection = new DiscoveryServiceConnection();
-
- public static NearbyBeaconsFragment newInstance() {
- return new NearbyBeaconsFragment();
- }
-
- private void initialize(View rootView) {
- setHasOptionsMenu(true);
- mUrlQueue = new ArrayList<>();
- mHandler = new Handler();
-
- mSwipeRefreshWidget = (SwipeRefreshWidget) rootView.findViewById(R.id.swipe_refresh_widget);
- mSwipeRefreshWidget.setColorSchemeResources(R.color.swipe_refresh_widget_first_color,
- R.color.swipe_refresh_widget_second_color);
- mSwipeRefreshWidget.setOnRefreshListener(this);
-
- getActivity().getActionBar().setTitle(R.string.title_nearby_beacons);
- mNearbyDeviceAdapter = new NearbyBeaconsAdapter();
- setListAdapter(mNearbyDeviceAdapter);
- //Get the top drawable
- mScanningAnimationTextView = (TextView) rootView.findViewById(android.R.id.empty);
- mScanningAnimationDrawable =
- (AnimationDrawable) mScanningAnimationTextView.getCompoundDrawables()[1];
- ListView listView = (ListView) rootView.findViewById(android.R.id.list);
- listView.setOnItemLongClickListener(mAdapterViewItemLongClickListener);
- }
-
- public View onCreateView(LayoutInflater layoutInflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = layoutInflater.inflate(R.layout.fragment_nearby_beacons, container, false);
- initialize(rootView);
- return rootView;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getActivity().getActionBar().setTitle(R.string.title_nearby_beacons);
- getActivity().getActionBar().setDisplayHomeAsUpEnabled(false);
- getListView().setVisibility(View.INVISIBLE);
- mDiscoveryServiceConnection.connect(true);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mDiscoveryServiceConnection.disconnect();
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- menu.findItem(R.id.action_config).setVisible(true);
- menu.findItem(R.id.action_about).setVisible(true);
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- // If we are scanning
- if (mScanningAnimationDrawable.isRunning()) {
- // Don't respond to touch events
- return;
- }
- // Get the url for the given item
- PwoMetadata pwoMetadata = mNearbyDeviceAdapter.getItem(position);
- Intent intent = pwoMetadata.createNavigateToUrlIntent();
- startActivity(intent);
- }
-
- @Override
- public void onPwoDiscoveryUpdate() {
- for (PwoMetadata pwoMetadata : mUrlToPwoMetadata.values()) {
- if (!mUrlQueue.contains(pwoMetadata.url)
- && !mNearbyDeviceAdapter.containsUrl(pwoMetadata.url)) {
- mUrlQueue.add(pwoMetadata.url);
- if (mSecondScanComplete) {
- // If we've already waited for the second scan timeout, go ahead and put the item in the
- // listview.
- emptyUrlQueue();
- }
- }
- }
- safeNotifyChange();
- }
-
- private void stopScanningDisplay() {
- // Cancel the scan timeout callback if still active or else it may fire later.
- mHandler.removeCallbacks(mFirstScanTimeout);
- mHandler.removeCallbacks(mSecondScanTimeout);
- mHandler.removeCallbacks(mThirdScanTimeout);
-
- // Change the display appropriately
- mSwipeRefreshWidget.setRefreshing(false);
- mScanningAnimationDrawable.stop();
- }
-
- private void startScanningDisplay(long scanStartTime, boolean hasResults) {
- // Start the scanning animation only if we don't haven't already been scanning
- // for long enough
- long elapsedMillis = new Date().getTime() - scanStartTime;
- if (elapsedMillis < FIRST_SCAN_TIME_MILLIS
- || (elapsedMillis < SECOND_SCAN_TIME_MILLIS && !hasResults)) {
- mScanningAnimationTextView.setAlpha(1f);
- mScanningAnimationDrawable.start();
- getListView().setVisibility(View.INVISIBLE);
- } else {
- showListView();
- }
-
- // Schedule the timeouts
- // We delay at least 50 milliseconds to give the discovery service a chance to
- // give us cached results.
- mSecondScanComplete = false;
- long firstDelay = Math.max(FIRST_SCAN_TIME_MILLIS - elapsedMillis, 50);
- long secondDelay = Math.max(SECOND_SCAN_TIME_MILLIS - elapsedMillis, 50);
- long thirdDelay = Math.max(THIRD_SCAN_TIME_MILLIS - elapsedMillis, 50);
- mHandler.postDelayed(mFirstScanTimeout, firstDelay);
- mHandler.postDelayed(mSecondScanTimeout, secondDelay);
- mHandler.postDelayed(mThirdScanTimeout, thirdDelay);
- }
-
- @Override
- public void onRefresh() {
- // Clear any stored url data
- mUrlQueue.clear();
- mNearbyDeviceAdapter.clear();
-
- // Reconnect to the service
- mDiscoveryServiceConnection.disconnect();
- mSwipeRefreshWidget.setRefreshing(true);
- mDiscoveryServiceConnection.connect(false);
- }
-
- private void emptyUrlQueue() {
- List pwoMetadataList = new ArrayList<>();
- for (String url : mUrlQueue) {
- pwoMetadataList.add(mUrlToPwoMetadata.get(url));
- }
- Collections.sort(pwoMetadataList);
- for (PwoMetadata pwoMetadata : pwoMetadataList) {
- mNearbyDeviceAdapter.addItem(pwoMetadata);
- }
- mUrlQueue.clear();
- safeNotifyChange();
- }
-
- private void showListView() {
- if (getListView().getVisibility() == View.VISIBLE) {
- return;
- }
-
- mSwipeRefreshWidget.setRefreshing(false);
- getListView().setAlpha(0f);
- getListView().setVisibility(View.VISIBLE);
- safeNotifyChange();
- ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(getListView(), "alpha", 0f, 1f);
- alphaAnimation.setDuration(400);
- alphaAnimation.setInterpolator(new DecelerateInterpolator());
- alphaAnimation.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mScanningAnimationTextView.setAlpha(0f);
- mScanningAnimationDrawable.stop();
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- });
- alphaAnimation.start();
- }
-
- /**
- * Notify the view that the underlying data has been changed.
- *
- * We need to make sure the view is visible because if it's not,
- * the view will become visible when we notify it.
- */
- private void safeNotifyChange() {
- if (getListView().getVisibility() == View.VISIBLE) {
- mNearbyDeviceAdapter.notifyDataSetChanged();
- }
- }
-
- // Adapter for holding beacons found through scanning.
- private class NearbyBeaconsAdapter extends BaseAdapter {
- private BeaconDisplayList mBeaconDisplayList;
-
- NearbyBeaconsAdapter() {
- mBeaconDisplayList = new BeaconDisplayList();
- }
-
- public void addItem(PwoMetadata pwoMetadata) {
- mBeaconDisplayList.addItem(pwoMetadata);
- }
-
- public boolean containsUrl(String url) {
- return mBeaconDisplayList.containsUrl(url);
- }
-
- @Override
- public int getCount() {
- return mBeaconDisplayList.size();
- }
-
- @Override
- public PwoMetadata getItem(int i) {
- return mBeaconDisplayList.getItem(i);
- }
-
- @Override
- public long getItemId(int i) {
- return i;
- }
-
- @SuppressLint("InflateParams")
- @Override
- public View getView(int i, View view, ViewGroup viewGroup) {
- // Get the list view item for the given position
- if (view == null) {
- view = getActivity().getLayoutInflater().inflate(R.layout.list_item_nearby_beacon,
- viewGroup, false);
- }
-
- // Reference the list item views
- TextView titleTextView = (TextView) view.findViewById(R.id.title);
- TextView urlTextView = (TextView) view.findViewById(R.id.url);
- TextView descriptionTextView = (TextView) view.findViewById(R.id.description);
- ImageView iconImageView = (ImageView) view.findViewById(R.id.icon);
-
- // Get the metadata for the given position
- PwoMetadata pwoMetadata = getItem(i);
- if (pwoMetadata.hasUrlMetadata()) {
- // If the url metadata exists
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- // Set the title text
- titleTextView.setText(urlMetadata.title);
- // Set the url text
- urlTextView.setText(urlMetadata.displayUrl);
- // Set the description text
- descriptionTextView.setText(urlMetadata.description);
- // Set the favicon image
- iconImageView.setImageBitmap(urlMetadata.icon);
- } else {
- // If metadata does not yet exist
- // Clear the children views content (in case this is a recycled list item view)
- titleTextView.setText("");
- iconImageView.setImageDrawable(null);
- // Set the url text to be the beacon's advertised url
- urlTextView.setText(pwoMetadata.url);
- // Set the description text to show loading status
- descriptionTextView.setText(R.string.metadata_loading);
- }
-
- if (mDebugViewEnabled) {
- // If we should show the ranging data
- updateDebugView(pwoMetadata, view);
- view.findViewById(R.id.ranging_debug_container).setVisibility(View.VISIBLE);
- view.findViewById(R.id.metadata_debug_container).setVisibility(View.VISIBLE);
- PwsClient.getInstance(getActivity()).useDevEndpoint();
- } else {
- // Otherwise ensure it is not shown
- view.findViewById(R.id.ranging_debug_container).setVisibility(View.GONE);
- view.findViewById(R.id.metadata_debug_container).setVisibility(View.GONE);
- PwsClient.getInstance(getActivity()).useProdEndpoint();
- }
-
- return view;
- }
-
- private void updateDebugView(PwoMetadata pwoMetadata, View view) {
- // Ranging debug line
- TextView txPowerView = (TextView) view.findViewById(R.id.ranging_debug_tx_power);
- TextView rssiView = (TextView) view.findViewById(R.id.ranging_debug_rssi);
- TextView distanceView = (TextView) view.findViewById(R.id.ranging_debug_distance);
- TextView regionView = (TextView) view.findViewById(R.id.ranging_debug_region);
- if (pwoMetadata.hasBleMetadata()) {
- BleMetadata bleMetadata = pwoMetadata.bleMetadata;
-
- int txPower = bleMetadata.txPower;
- String txPowerString = getString(R.string.ranging_debug_tx_power_prefix) + txPower;
- txPowerView.setText(txPowerString);
-
- String deviceAddress = bleMetadata.deviceAddress;
- int rssi = bleMetadata.getSmoothedRssi();
- String rssiString = getString(R.string.ranging_debug_rssi_prefix) + rssi;
- rssiView.setText(rssiString);
-
- double distance = bleMetadata.getDistance();
- String distanceString = getString(R.string.ranging_debug_distance_prefix)
- + new DecimalFormat("##.##").format(distance);
- distanceView.setText(distanceString);
-
- String region = bleMetadata.getRegionString();
- String regionString = getString(R.string.ranging_debug_region_prefix) + region;
- regionView.setText(regionString);
- } else {
- txPowerView.setText("");
- rssiView.setText("");
- distanceView.setText("");
- regionView.setText("");
- }
-
- // Metadata debug line
- double scanTime = pwoMetadata.scanMillis / 1000.0;
- String scanTimeString = getString(R.string.metadata_debug_scan_time_prefix)
- + new DecimalFormat("##.##s").format(scanTime);
- TextView scanTimeView = (TextView) view.findViewById(R.id.metadata_debug_scan_time);
- scanTimeView.setText(scanTimeString);
-
- TextView rankView = (TextView) view.findViewById(R.id.metadata_debug_rank);
- TextView pwsTripTimeView = (TextView) view.findViewById(R.id.metadata_debug_pws_trip_time);
- TextView groupidView = (TextView) view.findViewById(R.id.metadata_debug_groupid);
- if (pwoMetadata.hasUrlMetadata()) {
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- double rank = urlMetadata.rank;
- String rankString = getString(R.string.metadata_debug_rank_prefix)
- + new DecimalFormat("##.##").format(rank);
- rankView.setText(rankString);
-
- double pwsTripTime = pwoMetadata.pwsTripMillis / 1000.0;
- String pwsTripTimeString = "" + getString(R.string.metadata_debug_pws_trip_time_prefix)
- + new DecimalFormat("##.##s").format(pwsTripTime);
- pwsTripTimeView.setText(pwsTripTimeString);
-
- String groupidString = getString(R.string.metadata_debug_groupid_prefix)
- + urlMetadata.groupid;
- groupidView.setText(groupidString);
- } else {
- rankView.setText("");
- pwsTripTimeView.setText("");
- groupidView.setText("");
- }
- }
-
- public void clear() {
- mBeaconDisplayList.clear();
- notifyDataSetChanged();
- }
- }
-}
-
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/OobActivity.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/OobActivity.java
deleted file mode 100644
index c7d82c9a..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/OobActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.view.Menu;
-import android.view.View;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-
-/**
- * The out of the box activity that let's the user know what this app is all about.
- */
-public class OobActivity extends AppCompatActivity {
-
- View.OnClickListener mAcceptButtonOnClickListener = new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- // Save the opt in preference
- String preferencesKey = getString(R.string.main_prefs_key);
- SharedPreferences sharedPreferences = getSharedPreferences(preferencesKey,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.user_opted_in_flag), true);
- editor.apply();
-
- // Exit the activity
- finish();
- }
- };
-
- @SuppressLint("SetJavaScriptEnabled")
- private void initializeWebView() {
- WebView webView = (WebView) findViewById(R.id.oob_webview);
- // Force the background color to update (it uses the color specified in the layout xml)
- webView.setBackgroundColor(0x000);
- webView.getSettings().setJavaScriptEnabled(true);
- webView.setWebViewClient(new WebViewClient());
- webView.loadUrl(getString(R.string.url_getting_started));
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_oob);
- findViewById(R.id.oob_accept_button).setOnClickListener(mAcceptButtonOnClickListener);
- initializeWebView();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_oob, menu);
- return true;
- }
-
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- // Make sure that the back button brings the user to the home screen
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_HOME);
- startActivity(intent);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoverer.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoverer.java
deleted file mode 100644
index 576ed519..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoverer.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import java.util.Date;
-
-abstract class PwoDiscoverer {
-
- private PwoDiscoveryCallback mPwoDiscoveryCallback;
- private long mScanStartTime;
-
- public abstract void startScanImpl();
- public abstract void stopScanImpl();
-
- public void startScan() {
- mScanStartTime = new Date().getTime();
- startScanImpl();
- }
-
- public void stopScan() {
- stopScanImpl();
- }
-
- public void restartScan() {
- stopScan();
- startScan();
- }
-
- public void setCallback(PwoDiscoveryCallback pwoDiscoveryCallback) {
- mPwoDiscoveryCallback = pwoDiscoveryCallback;
- }
-
- protected PwoMetadata createPwoMetadata(String url) {
- PwoMetadata pwoMetadata = new PwoMetadata(url, new Date().getTime() - mScanStartTime);
- return pwoMetadata;
- }
-
- protected void reportPwo(PwoMetadata pwoMetadata) {
- mPwoDiscoveryCallback.onPwoDiscovered(pwoMetadata);
- }
-
- public interface PwoDiscoveryCallback {
- public void onPwoDiscovered(PwoMetadata pwoMetadata);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoveryService.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoveryService.java
deleted file mode 100644
index c779734c..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoDiscoveryService.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.PwoMetadata.UrlMetadata;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import org.json.JSONException;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This is a service that scans for nearby Physical Web Objects.
- * It is created by MainActivity.
- * It finds nearby ble beacons,
- * and stores a count of them.
- * It also listens for screen on/off events
- * and start/stops the scanning accordingly.
- * It also silently issues a notification
- * informing the user of nearby beacons.
- * As beacons are found and lost,
- * the notification is updated to reflect
- * the current number of nearby beacons.
- */
-
-public class PwoDiscoveryService extends Service
- implements PwsClient.ResolveScanCallback,
- PwsClient.DownloadIconCallback,
- PwoDiscoverer.PwoDiscoveryCallback {
-
- private static final String TAG = "PwoDiscoveryService";
- private static final String NOTIFICATION_GROUP_KEY = "URI_BEACON_NOTIFICATIONS";
- private static final String PREFS_VERSION_KEY = "prefs_version";
- private static final String SCAN_START_TIME_KEY = "scan_start_time";
- private static final String PWO_METADATA_KEY = "pwo_metadata";
- private static final int PREFS_VERSION = 1;
- private static final int NEAREST_BEACON_NOTIFICATION_ID = 23;
- private static final int SECOND_NEAREST_BEACON_NOTIFICATION_ID = 24;
- private static final int SUMMARY_NOTIFICATION_ID = 25;
- private static final int NON_LOLLIPOP_NOTIFICATION_TITLE_COLOR = Color.parseColor("#ffffff");
- private static final int NON_LOLLIPOP_NOTIFICATION_URL_COLOR = Color.parseColor("#999999");
- private static final int NON_LOLLIPOP_NOTIFICATION_SNIPPET_COLOR = Color.parseColor("#999999");
- private static final int NOTIFICATION_PRIORITY = NotificationCompat.PRIORITY_MIN;
- private static final int NOTIFICATION_VISIBILITY = NotificationCompat.VISIBILITY_PUBLIC;
- private static final long FIRST_SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(2);
- private static final long SECOND_SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(10);
- private static final long SCAN_STALE_TIME_MILLIS = TimeUnit.MINUTES.toMillis(2);
- private boolean mCanUpdateNotifications = false;
- private boolean mSecondScanComplete = false;
- private boolean mIsBound = false;
- private long mScanStartTime;
- private Handler mHandler;
- private NotificationManagerCompat mNotificationManager;
- private HashMap mUrlToPwoMetadata;
- private List mPwoDiscoverers;
- private List mPwoResponseCallbacks;
-
- // Notification of urls happens as follows:
- // 0. Begin scan
- // 1. Delete notification, show top two urls (mFirstScanTimeout)
- // 2. Show each new url as it comes in, if it's in the top two
- // 3. Stop scanning if no clients are subscribed (mSecondScanTimeout)
-
- private Runnable mFirstScanTimeout = new Runnable() {
- @Override
- public void run() {
- mCanUpdateNotifications = true;
- updateNotifications();
- }
- };
-
- private Runnable mSecondScanTimeout = new Runnable() {
- @Override
- public void run() {
- mSecondScanComplete = true;
- if (!mIsBound) {
- stopSelf();
- }
- }
- };
-
- /**
- * Binder class for getting connections to the service.
- */
- public class LocalBinder extends Binder {
- public PwoDiscoveryService getServiceInstance() {
- return PwoDiscoveryService.this;
- }
- }
- private IBinder mBinder = new LocalBinder();
-
- /**
- * Callback for subscribers to this service.
- */
- public interface PwoResponseCallback {
- public void onPwoDiscoveryUpdate();
- }
-
- public PwoDiscoveryService() {
- }
-
- private void initialize() {
- mNotificationManager = NotificationManagerCompat.from(this);
- mPwoDiscoverers = new ArrayList<>();
-
- // disable mDNS PWO discovery for pre-M devices
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
- mPwoDiscoverers.add(new MdnsPwoDiscoverer(this));
- }
- mPwoDiscoverers.add(new SsdpPwoDiscoverer(this));
- mPwoDiscoverers.add(new BlePwoDiscoverer(this));
- for (PwoDiscoverer pwoDiscoverer : mPwoDiscoverers) {
- pwoDiscoverer.setCallback(this);
- }
- mPwoResponseCallbacks = new ArrayList<>();
- mHandler = new Handler();
- mUrlToPwoMetadata = new HashMap<>();
- mCanUpdateNotifications = false;
- }
-
- private void restoreCache() {
- // Make sure we are trying to load the right version of the cache
- String preferencesKey = getString(R.string.discovery_service_prefs_key);
- SharedPreferences prefs = getSharedPreferences(preferencesKey, Context.MODE_PRIVATE);
- int prefsVersion = prefs.getInt(PREFS_VERSION_KEY, 0);
- long now = new Date().getTime();
- if (prefsVersion != PREFS_VERSION) {
- mScanStartTime = now;
- return;
- }
-
- // Don't load the cache if it's stale
- mScanStartTime = prefs.getLong(SCAN_START_TIME_KEY, 0);
- if (now - mScanStartTime >= SCAN_STALE_TIME_MILLIS) {
- mScanStartTime = now;
- return;
- }
-
- // Restore the cached metadata
- Set serializedPwoMetadata = prefs.getStringSet(PWO_METADATA_KEY,
- new HashSet());
- for (String pwoMetadataStr : serializedPwoMetadata) {
- PwoMetadata pwoMetadata;
- try {
- pwoMetadata = PwoMetadata.fromJsonStr(pwoMetadataStr);
- } catch (JSONException e) {
- Log.e(TAG, "Could not deserialize PWO cache item: " + e);
- continue;
- }
- onPwoDiscovered(pwoMetadata);
- if (pwoMetadata.hasBleMetadata()) {
- pwoMetadata.bleMetadata.updateRegionInfo();
- }
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- initialize();
- restoreCache();
-
- mNotificationManager.cancelAll();
- mHandler.postDelayed(mFirstScanTimeout, FIRST_SCAN_TIME_MILLIS);
- mHandler.postDelayed(mSecondScanTimeout, SECOND_SCAN_TIME_MILLIS);
- for (PwoDiscoverer pwoDiscoverer : mPwoDiscoverers) {
- pwoDiscoverer.startScan();
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- mIsBound = true;
- return mBinder;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mIsBound = false;
- if (mSecondScanComplete) {
- stopSelf();
- }
- // true ensures onRebind is called on succcessive binds
- return true;
- }
-
- @Override
- public void onRebind(Intent intent) {
- mIsBound = true;
- }
-
- private void saveCache() {
- // Serialize the PwoMetadata
- Set serializedPwoMetadata = new HashSet<>();
- for (PwoMetadata pwoMetadata : mUrlToPwoMetadata.values()) {
- try {
- serializedPwoMetadata.add(pwoMetadata.toJsonStr());
- } catch (JSONException e) {
- Log.e(TAG, "Could not serialize PWO cache item: " + e);
- continue;
- }
- }
-
- // Write the PwoMetadata
- String preferencesKey = getString(R.string.discovery_service_prefs_key);
- SharedPreferences prefs = getSharedPreferences(preferencesKey, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(PREFS_VERSION_KEY, PREFS_VERSION);
- editor.putLong(SCAN_START_TIME_KEY, mScanStartTime);
- editor.putStringSet(PWO_METADATA_KEY, serializedPwoMetadata);
- editor.apply();
- }
-
- @Override
- public void onDestroy() {
- Log.d(TAG, "onDestroy: service exiting");
-
- // Stop the scanners
- mHandler.removeCallbacks(mFirstScanTimeout);
- mHandler.removeCallbacks(mSecondScanTimeout);
- for (PwoDiscoverer pwoDiscoverer : mPwoDiscoverers) {
- pwoDiscoverer.stopScan();
- }
-
- saveCache();
- super.onDestroy();
- }
-
- @Override
- public void onUrlMetadataReceived(PwoMetadata pwoMetadata) {
- triggerCallback();
- if (!pwoMetadata.urlMetadata.iconUrl.isEmpty()) {
- PwsClient.getInstance(this).downloadIcon(pwoMetadata, this);
- }
- updateNotifications();
- }
-
- @Override
- public void onUrlMetadataAbsent(PwoMetadata pwoMetadata) {
- triggerCallback();
- }
-
- @Override
- public void onUrlMetadataIconReceived(PwoMetadata pwoMetadata) {
- triggerCallback();
- updateNotifications();
- }
-
- @Override
- public void onUrlMetadataIconError(PwoMetadata pwoMetadata) {
- triggerCallback();
- }
-
- @Override
- public void onPwoDiscovered(PwoMetadata pwoMetadata) {
- PwoMetadata storedPwoMetadata = mUrlToPwoMetadata.get(pwoMetadata.url);
- if (storedPwoMetadata == null) {
- mUrlToPwoMetadata.put(pwoMetadata.url, pwoMetadata);
- // We need to make sure the urlMetadata is populated. This could be a fresh pwoMetadata for
- // which we have not fetched urlMetadata, or it could be a cached pwoMetadata for which the
- // urlMetadata fetching process was prematurely terminated.
- if (pwoMetadata.hasUrlMetadata()) {
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- if (urlMetadata.icon == null && !urlMetadata.iconUrl.isEmpty()) {
- PwsClient.getInstance(this).downloadIcon(pwoMetadata, this);
- }
- } else {
- PwsClient.getInstance(this).findUrlMetadata(pwoMetadata, this, TAG);
- }
- storedPwoMetadata = pwoMetadata;
- }
-
- triggerCallback();
- }
-
- private void triggerCallback() {
- for (PwoResponseCallback pwoResponseCallback : mPwoResponseCallbacks) {
- pwoResponseCallback.onPwoDiscoveryUpdate();
- }
- }
-
- /**
- * Create a new set of notifications or update those existing.
- */
- private void updateNotifications() {
- if (!mCanUpdateNotifications) {
- return;
- }
-
- List pwoMetadataList = getSortedPwoMetadataWithUrlMetadata();
-
- // If no beacons have been found
- if (pwoMetadataList.size() == 0) {
- // Remove all existing notifications
- mNotificationManager.cancelAll();
- } else if (pwoMetadataList.size() == 1) {
- updateNearbyBeaconNotification(true, pwoMetadataList.get(0), NEAREST_BEACON_NOTIFICATION_ID);
- } else {
- // Create a summary notification for both beacon notifications.
- // Do this first so that we don't first show the individual notifications
- updateSummaryNotification(pwoMetadataList);
- // Create or update a notification for second beacon
- updateNearbyBeaconNotification(false, pwoMetadataList.get(1),
- SECOND_NEAREST_BEACON_NOTIFICATION_ID);
- // Create or update a notification for first beacon. Needs to be added last to show up top
- updateNearbyBeaconNotification(false, pwoMetadataList.get(0),
- NEAREST_BEACON_NOTIFICATION_ID);
-
- }
- }
-
- private List getSortedPwoMetadataWithUrlMetadata() {
- List pwoMetadataList = new ArrayList<>(mUrlToPwoMetadata.size());
- for (PwoMetadata pwoMetadata : mUrlToPwoMetadata.values()) {
- if (pwoMetadata.hasUrlMetadata()) {
- pwoMetadataList.add(pwoMetadata);
- }
- }
- Collections.sort(pwoMetadataList);
- return pwoMetadataList;
- }
-
- /**
- * Create or update a notification with the given id for the beacon with the given address.
- */
- private void updateNearbyBeaconNotification(boolean single, PwoMetadata pwoMetadata,
- int notificationId) {
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- // Create an intent that will open the browser to the beacon's url
- // if the user taps on the notification
- PendingIntent pendingIntent = pwoMetadata.createNavigateToUrlPendingIntent(this);
-
- String title = urlMetadata.title;
- String description = urlMetadata.description;
- Bitmap icon = urlMetadata.icon;
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.ic_notification)
- .setLargeIcon(icon)
- .setContentTitle(title)
- .setContentText(description)
- .setPriority(NOTIFICATION_PRIORITY)
- .setContentIntent(pendingIntent);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && pwoMetadata.isPublic) {
- builder.setVisibility(NOTIFICATION_VISIBILITY);
- }
- // For some reason if there is only one notification and you call setGroup
- // the notification doesn't show up on the N7 running kit kat
- if (!single) {
- builder = builder.setGroup(NOTIFICATION_GROUP_KEY);
- }
- Notification notification = builder.build();
-
- mNotificationManager.notify(notificationId, notification);
- }
-
- /**
- * Create or update the a single notification that is a collapsed version
- * of the top two beacon notifications.
- */
- private void updateSummaryNotification(List pwoMetadataList) {
- int numNearbyBeacons = pwoMetadataList.size();
- String contentTitle = String.valueOf(numNearbyBeacons);
- Resources resources = getResources();
- contentTitle += " " + resources.getQuantityString(R.plurals.numFoundBeacons, numNearbyBeacons,
- numNearbyBeacons);
- String contentText = getString(R.string.summary_notification_pull_down);
- PendingIntent pendingIntent = createReturnToAppPendingIntent();
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.ic_notification)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setSmallIcon(R.drawable.ic_notification)
- .setGroup(NOTIFICATION_GROUP_KEY)
- .setGroupSummary(true)
- .setPriority(NOTIFICATION_PRIORITY)
- .setContentIntent(pendingIntent);
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- builder.setVisibility(NOTIFICATION_VISIBILITY);
- }
- Notification notification = builder.build();
-
- // Create the big view for the notification (viewed by pulling down)
- RemoteViews remoteViews = updateSummaryNotificationRemoteViews(pwoMetadataList);
- notification.bigContentView = remoteViews;
-
- mNotificationManager.notify(SUMMARY_NOTIFICATION_ID, notification);
- }
-
- /**
- * Create the big view for the summary notification.
- */
- private RemoteViews updateSummaryNotificationRemoteViews(List pwoMetadataList) {
- RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification_big_view);
-
- // Fill in the data for the top two beacon views
- updateSummaryNotificationRemoteViewsFirstBeacon(pwoMetadataList.get(0), remoteViews);
- updateSummaryNotificationRemoteViewsSecondBeacon(pwoMetadataList.get(1), remoteViews);
-
- // Create a pending intent that will open the physical web app
- // TODO(cco3): Use a clickListener on the VIEW MORE button to do this
- PendingIntent pendingIntent = createReturnToAppPendingIntent();
- remoteViews.setOnClickPendingIntent(R.id.otherBeaconsLayout, pendingIntent);
-
- return remoteViews;
- }
-
- private void updateSummaryNotificationRemoteViewsFirstBeacon(PwoMetadata pwoMetadata,
- RemoteViews remoteViews) {
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- remoteViews.setImageViewBitmap(R.id.icon_firstBeacon, urlMetadata.icon);
- remoteViews.setTextViewText(R.id.title_firstBeacon, urlMetadata.title);
- remoteViews.setTextViewText(R.id.url_firstBeacon, urlMetadata.displayUrl);
- remoteViews.setTextViewText(R.id.description_firstBeacon, urlMetadata.description);
- // Recolor notifications to have light text for non-Lollipop devices
- if (!(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
- remoteViews.setTextColor(R.id.title_firstBeacon, NON_LOLLIPOP_NOTIFICATION_TITLE_COLOR);
- remoteViews.setTextColor(R.id.url_firstBeacon, NON_LOLLIPOP_NOTIFICATION_URL_COLOR);
- remoteViews.setTextColor(R.id.description_firstBeacon,
- NON_LOLLIPOP_NOTIFICATION_SNIPPET_COLOR);
- }
-
- // Create an intent that will open the browser to the beacon's url
- // if the user taps the notification
- PendingIntent pendingIntent = pwoMetadata.createNavigateToUrlPendingIntent(this);
- remoteViews.setOnClickPendingIntent(R.id.first_beacon_main_layout, pendingIntent);
- remoteViews.setViewVisibility(R.id.firstBeaconLayout, View.VISIBLE);
- }
-
- private void updateSummaryNotificationRemoteViewsSecondBeacon(PwoMetadata pwoMetadata,
- RemoteViews remoteViews) {
- UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- remoteViews.setImageViewBitmap(R.id.icon_secondBeacon, urlMetadata.icon);
- remoteViews.setTextViewText(R.id.title_secondBeacon, urlMetadata.title);
- remoteViews.setTextViewText(R.id.url_secondBeacon, urlMetadata.displayUrl);
- remoteViews.setTextViewText(R.id.description_secondBeacon, urlMetadata.description);
- // Recolor notifications to have light text for non-Lollipop devices
- if (!(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
- remoteViews.setTextColor(R.id.title_secondBeacon, NON_LOLLIPOP_NOTIFICATION_TITLE_COLOR);
- remoteViews.setTextColor(R.id.url_secondBeacon, NON_LOLLIPOP_NOTIFICATION_URL_COLOR);
- remoteViews.setTextColor(R.id.description_secondBeacon,
- NON_LOLLIPOP_NOTIFICATION_SNIPPET_COLOR);
- }
-
- // Create an intent that will open the browser to the beacon's url
- // if the user taps the notification
- PendingIntent pendingIntent = pwoMetadata.createNavigateToUrlPendingIntent(this);
- remoteViews.setOnClickPendingIntent(R.id.second_beacon_main_layout, pendingIntent);
- remoteViews.setViewVisibility(R.id.secondBeaconLayout, View.VISIBLE);
- }
-
- private PendingIntent createReturnToAppPendingIntent() {
- Intent intent = new Intent(this, MainActivity.class);
- int requestID = (int) System.currentTimeMillis();
- PendingIntent pendingIntent = PendingIntent.getActivity(this, requestID, intent, 0);
- return pendingIntent;
- }
-
- public void addCallback(PwoResponseCallback pwoResponseCallback) {
- mPwoResponseCallbacks.add(pwoResponseCallback);
- }
-
- public void removeCallback(PwoResponseCallback pwoResponseCallback) {
- mPwoResponseCallbacks.remove(pwoResponseCallback);
- }
-
- public long getScanStartTime() {
- return mScanStartTime;
- }
-
- public void restartScan() {
- for (PwoDiscoverer pwoDiscoverer : mPwoDiscoverers) {
- pwoDiscoverer.stopScan();
- }
- mScanStartTime = new Date().getTime();
- for (PwoDiscoverer pwoDiscoverer : mPwoDiscoverers) {
- pwoDiscoverer.startScan();
- }
- }
-
- public boolean hasResults() {
- return !mUrlToPwoMetadata.isEmpty();
- }
-
- public HashMap getUrlToPwoMetadataMap() {
- return mUrlToPwoMetadata;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoMetadata.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoMetadata.java
deleted file mode 100644
index 18caea04..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwoMetadata.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.util.Base64;
-import android.webkit.URLUtil;
-
-import org.uribeacon.scan.util.RangingUtils;
-import org.uribeacon.scan.util.RegionResolver;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-
-/**
- * This class holds data about a Physical Web Object and its url.
- *
- * A physical web object is any source of a broadcasted url.
- */
-class PwoMetadata implements Comparable {
- private static final String URL_KEY = "deviceAddress";
- private static final String IS_PUBLIC_KEY = "isPublic";
- private static final String SCAN_MILLIS_KEY = "scanMillis";
- private static final String PWS_TRIP_MILLIS_KEY = "pwsTripMillis";
- private static final String BLE_METADATA_KEY = "bleMetadata";
- private static final String URL_METADATA_KEY = "urlMetadata";
- String url;
- boolean isPublic;
- long scanMillis;
- long pwsTripMillis;
- BleMetadata bleMetadata;
- UrlMetadata urlMetadata;
-
- /**
- * A container class for ble-specific metadata.
- */
- public static class BleMetadata {
- private static final String DEVICE_ADDRESS_KEY = "deviceAddress";
- private static final String RSSI_KEY = "rssi";
- private static final String TX_POWER_KEY = "txPower";
- String deviceAddress;
- int rssi;
- int txPower;
-
- private static RegionResolver sRegionResolver;
-
- public BleMetadata(String deviceAddress, int rssi, int txPower) {
- this.deviceAddress = deviceAddress;
- this.rssi = rssi;
- this.txPower = txPower;
- }
-
- public JSONObject toJsonObj() throws JSONException {
- JSONObject jsonObj = new JSONObject();
- jsonObj.put(DEVICE_ADDRESS_KEY, deviceAddress);
- jsonObj.put(RSSI_KEY, rssi);
- jsonObj.put(TX_POWER_KEY, txPower);
- return jsonObj;
- }
-
- public String toJsonStr() throws JSONException {
- return toJsonObj().toString();
- }
-
- public static BleMetadata fromJsonObj(JSONObject jsonObj) throws JSONException {
- BleMetadata bleMetadata = new BleMetadata(
- jsonObj.getString(DEVICE_ADDRESS_KEY),
- jsonObj.getInt(RSSI_KEY),
- jsonObj.getInt(TX_POWER_KEY));
- return bleMetadata;
- }
-
- public static BleMetadata fromJsonStr(String jsonStr) throws JSONException {
- return fromJsonObj(new JSONObject(jsonStr));
- }
-
- private static RegionResolver getRegionResolver() {
- if (sRegionResolver == null) {
- sRegionResolver = new RegionResolver();
- }
- return sRegionResolver;
- }
-
- public void updateRegionInfo() {
- getRegionResolver().onUpdate(this.deviceAddress, this.rssi, this.txPower);
- }
-
- public int getSmoothedRssi() {
- return getRegionResolver().getSmoothedRssi(deviceAddress);
- }
-
- public double getDistance() {
- return getRegionResolver().getDistance(deviceAddress);
- }
-
- public int getRegion() {
- return getRegionResolver().getRegion(deviceAddress);
- }
-
- public String getRegionString() {
- return RangingUtils.toString(getRegion());
- }
- }
-
- /**
- * A container class for a url's fetched metadata.
- * The metadata consists of the title, site url, description,
- * iconUrl and the icon (or favicon).
- * This data is scraped via a server that receives a url
- * and returns a json blob.
- */
- public static class UrlMetadata implements Comparable{
- private static final String ID_KEY = "id";
- private static final String SITE_URL_KEY = "siteUrl";
- private static final String DISPLAY_URL_KEY = "displayUrl";
- private static final String TITLE_KEY = "title";
- private static final String DESCRIPTION_KEY = "description";
- private static final String ICON_URL_KEY = "iconUrl";
- private static final String ICON_KEY = "icon";
- private static final String RANK_KEY = "rank";
- private static final String GROUPID_KEY = "groupid";
- public String id;
- public String siteUrl;
- public String displayUrl;
- public String title;
- public String description;
- public String iconUrl;
- public Bitmap icon;
- public double rank;
- public String groupid;
-
- public UrlMetadata() {
- }
-
- public int compareTo(UrlMetadata other) {
- int rankCompare = ((Double) rank).compareTo(other.rank);
- if (rankCompare != 0) {
- return rankCompare;
- }
-
- // If ranks are equal, compare based on title
- return title.compareTo(other.title);
- }
-
- public JSONObject toJsonObj() throws JSONException {
- JSONObject jsonObj = new JSONObject();
- jsonObj.put(ID_KEY, id);
- jsonObj.put(SITE_URL_KEY, siteUrl);
- jsonObj.put(DISPLAY_URL_KEY, displayUrl);
- jsonObj.put(TITLE_KEY, title);
- jsonObj.put(DESCRIPTION_KEY, description);
- jsonObj.put(ICON_URL_KEY, iconUrl);
- if (icon != null) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- icon.compress(Bitmap.CompressFormat.PNG, 100, stream);
- byte[] bitmapData = stream.toByteArray();
- jsonObj.put(ICON_KEY, Base64.encodeToString(bitmapData, Base64.DEFAULT));
- }
- jsonObj.put(RANK_KEY, rank);
- jsonObj.put(GROUPID_KEY, groupid);
- return jsonObj;
- }
-
- public String toJsonStr() throws JSONException {
- return toJsonObj().toString();
- }
-
- public static UrlMetadata fromJsonObj(JSONObject jsonObj) throws JSONException {
- UrlMetadata urlMetadata = new UrlMetadata();
- urlMetadata.id = jsonObj.getString(ID_KEY);
- urlMetadata.siteUrl = jsonObj.getString(SITE_URL_KEY);
- urlMetadata.displayUrl = jsonObj.getString(DISPLAY_URL_KEY);
- urlMetadata.title = jsonObj.getString(TITLE_KEY);
- urlMetadata.description = jsonObj.getString(DESCRIPTION_KEY);
- urlMetadata.iconUrl = jsonObj.getString(ICON_URL_KEY);
- if (jsonObj.has(ICON_KEY)) {
- byte[] bitmapData = Base64.decode(jsonObj.getString(ICON_KEY), Base64.DEFAULT);
- urlMetadata.icon = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);
- }
- urlMetadata.rank = jsonObj.getDouble(RANK_KEY);
- urlMetadata.groupid = jsonObj.getString(GROUPID_KEY);
- return urlMetadata;
- }
-
- public static UrlMetadata fromJsonStr(String jsonStr) throws JSONException {
- return fromJsonObj(new JSONObject(jsonStr));
- }
- }
-
- public PwoMetadata(String url, long scanMillis) {
- this.url = url;
- this.scanMillis = scanMillis;
- // Default isPublic to true
- isPublic = true;
- }
-
- public void setUrlMetadata(UrlMetadata urlMetadata, long pwsTripMillis) {
- this.urlMetadata = urlMetadata;
- this.pwsTripMillis = pwsTripMillis;
- }
-
- public void setBleMetadata(String deviceAddress, int rssi, int txPower) {
- this.bleMetadata = new BleMetadata(deviceAddress, rssi, txPower);
- }
-
- public boolean hasBleMetadata() {
- return bleMetadata != null;
- }
-
- public boolean hasUrlMetadata() {
- return urlMetadata != null;
- }
-
- public String getNavigableUrl() {
- String urlToNavigateTo = url;
- if (hasUrlMetadata()) {
- String siteUrl = urlMetadata.siteUrl;
- if (siteUrl != null) {
- urlToNavigateTo = siteUrl;
- }
- }
- if (!URLUtil.isNetworkUrl(urlToNavigateTo)) {
- urlToNavigateTo = "http://" + urlToNavigateTo;
- }
- return urlToNavigateTo;
- }
-
- public String getGroupid() {
- if (hasUrlMetadata()) {
- String groupid = urlMetadata.groupid;
- if (groupid != null && !groupid.equals("")) {
- return groupid;
- }
- }
- return null;
- }
-
- public String getDeviceAddress() {
- if (hasBleMetadata()) {
- String deviceAddress = bleMetadata.deviceAddress;
- if (deviceAddress != null && !deviceAddress.equals("")) {
- return deviceAddress;
- }
- }
- return null;
- }
-
- public Intent createNavigateToUrlIntent() {
- String urlToNavigateTo = getNavigableUrl();
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(urlToNavigateTo));
- return intent;
- }
-
- public PendingIntent createNavigateToUrlPendingIntent(Context context) {
- Intent intent = createNavigateToUrlIntent();
- int requestID = (int) System.currentTimeMillis();
- return PendingIntent.getActivity(context, requestID, intent, 0);
- }
-
- public int compareTo(PwoMetadata other) {
- // Give preference to the PWO that has url metatada
- if (!hasUrlMetadata()) {
- return 1;
- }
- if (!other.hasUrlMetadata()) {
- return -1;
- }
- return urlMetadata.compareTo(other.urlMetadata);
- }
-
- public JSONObject toJsonObj() throws JSONException {
- JSONObject jsonObj = new JSONObject();
- jsonObj.put(URL_KEY, url);
- jsonObj.put(SCAN_MILLIS_KEY, scanMillis);
- jsonObj.put(PWS_TRIP_MILLIS_KEY, pwsTripMillis);
- if (hasBleMetadata()) {
- jsonObj.put(BLE_METADATA_KEY, bleMetadata.toJsonObj());
- }
- if (hasUrlMetadata()) {
- jsonObj.put(URL_METADATA_KEY, urlMetadata.toJsonObj());
- }
- return jsonObj;
- }
-
- public String toJsonStr() throws JSONException {
- return toJsonObj().toString();
- }
-
- public static PwoMetadata fromJsonObj(JSONObject jsonObj) throws JSONException {
- PwoMetadata pwoMetadata = new PwoMetadata(jsonObj.getString(URL_KEY),
- jsonObj.getLong(SCAN_MILLIS_KEY));
- pwoMetadata.pwsTripMillis = jsonObj.getLong(PWS_TRIP_MILLIS_KEY);
- if (jsonObj.has(BLE_METADATA_KEY)) {
- pwoMetadata.bleMetadata = BleMetadata.fromJsonObj(jsonObj.getJSONObject(BLE_METADATA_KEY));
- }
- if (jsonObj.has(URL_METADATA_KEY)) {
- pwoMetadata.urlMetadata = UrlMetadata.fromJsonObj(jsonObj.getJSONObject(URL_METADATA_KEY));
- }
- return pwoMetadata;
- }
-
- public static PwoMetadata fromJsonStr(String jsonStr) throws JSONException {
- return fromJsonObj(new JSONObject(jsonStr));
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwsClient.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwsClient.java
deleted file mode 100644
index 1a2a9430..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/PwsClient.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.PwoMetadata.BleMetadata;
-import org.physical_web.physicalweb.PwoMetadata.UrlMetadata;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import com.android.volley.RequestQueue;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-import com.android.volley.toolbox.ImageRequest;
-import com.android.volley.toolbox.JsonObjectRequest;
-import com.android.volley.toolbox.Volley;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class sends requests to the physical web service.
- *
- * For the largest use, metadata requests, the physical web service
- * scrapes the page at the given url for its metadata.
- */
-class PwsClient {
- private static final String TAG = "PwsClient";
- private static final String PROD_URL = "https://url-caster.appspot.com";
- private static final String DEV_URL = "https://url-caster-dev.appspot.com";
- private static final String RESOLVE_SCAN_PATH = "resolve-scan";
- private static final String SHORTEN_URL_PATH = "shorten-url";
- private static final int UNDEFINED_SCORE = -1;
- private RequestQueue mRequestQueue;
- private Context mContext;
- private String mEndpointUrl;
-
- private static PwsClient mInstance;
-
- private PwsClient(Context context) {
- mContext = context.getApplicationContext();
- mRequestQueue = Volley.newRequestQueue(mContext);
- mEndpointUrl = PROD_URL;
- }
-
- public static PwsClient getInstance(Context context) {
- if (mInstance == null) {
- mInstance = new PwsClient(context);
- }
- return mInstance;
- }
-
-
- /////////////////////////////////
- // callbacks
- /////////////////////////////////
-
- public interface ResolveScanCallback {
- public void onUrlMetadataReceived(PwoMetadata pwoMetadata);
- public void onUrlMetadataAbsent(PwoMetadata pwoMetadata);
- }
-
- public interface DownloadIconCallback {
- public void onUrlMetadataIconReceived(PwoMetadata pwoMetadata);
- public void onUrlMetadataIconError(PwoMetadata pwoMetadata);
- }
-
- public interface ShortenUrlCallback {
- public void onUrlShortened(String shortUrl);
- public void onError(String longUrl);
- }
-
-
- /////////////////////////////////
- // utilities
- /////////////////////////////////
-
- private String constructUrlStr(final String path) {
- return mEndpointUrl + "/" + path;
- }
-
- public void shortenUrl(final String longUrl,
- final ShortenUrlCallback shortenUrlCallback,
- final String tag) {
- // Create the json payload
- JSONObject jsonObject = new JSONObject();
- try {
- jsonObject.put("longUrl", longUrl);
- } catch (JSONException e) {
- Log.e(TAG, "JSONException: " + e.toString());
- shortenUrlCallback.onError(longUrl);
- return;
- }
-
- // Create the http request
- JsonObjectRequest request = new JsonObjectRequest(
- constructUrlStr(SHORTEN_URL_PATH),
- jsonObject,
- new Response.Listener() {
- @Override
- public void onResponse(JSONObject jsonResponse) {
- String shortUrl;
- try {
- shortUrl = jsonResponse.getString("id");
- } catch (JSONException e) {
- Log.e(TAG, "JSONException: " + e.toString());
- shortenUrlCallback.onError(longUrl);
- return;
- }
- shortenUrlCallback.onUrlShortened(shortUrl);
- }
- },
- new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError volleyError) {
- Log.i(TAG, "VolleyError: " + volleyError.toString());
- shortenUrlCallback.onError(longUrl);
- }
- }
- );
- request.setTag(tag);
-
- // Send off the request
- mRequestQueue.add(request);
- }
-
- public void findUrlMetadata(PwoMetadata pwoMetadata,
- ResolveScanCallback resolveScanCallback,
- final String tag) {
- List pwoMetadataList = Arrays.asList(pwoMetadata);
- findUrlMetadata(pwoMetadataList, resolveScanCallback, tag);
- }
-
- public void findUrlMetadata(Collection pwoMetadataCollection,
- ResolveScanCallback resolveScanCallback,
- final String tag) {
- // Create the metadata request
- // for the given json request object
- JsonObjectRequest request = createUrlMetadataRequest(pwoMetadataCollection,
- resolveScanCallback);
- request.setTag(tag);
- // Queue the request
- mRequestQueue.add(request);
- }
-
- /**
- * Create the json request object
- * that will be sent to the metadata server
- * asking for metadata for the given url.
- *
- * @param url The url for which the request data will be created
- * @return The constructed json object
- */
- private static JSONObject createUrlMetadataRequestObject(
- Collection pwoMetadataCollection) {
- JSONArray urlJsonArray = new JSONArray();
- for (PwoMetadata pwoMetadata : pwoMetadataCollection) {
- try {
- JSONObject urlJsonObject = new JSONObject();
- urlJsonObject.put("url", pwoMetadata.url);
- if (pwoMetadata.hasBleMetadata()) {
- BleMetadata bleMetadata = pwoMetadata.bleMetadata;
- urlJsonObject.put("txpower", bleMetadata.txPower);
- urlJsonObject.put("rssi", bleMetadata.rssi);
- }
- urlJsonArray.put(urlJsonObject);
- } catch (JSONException ex) {
- Log.d(TAG, "error: " + ex);
- }
- }
-
- JSONObject jsonObject = new JSONObject();
- try {
- jsonObject.put("objects", urlJsonArray);
- } catch (JSONException ex) {
- Log.d(TAG, "error: " + ex);
- }
- return jsonObject;
- }
-
- /**
- * Create the url metadata request, given the json request object.
- *
- * @param jsonObj The given json object to use in the request
- * @return The created json request object
- */
- private JsonObjectRequest createUrlMetadataRequest(
- final Collection pwoMetadataCollection,
- final ResolveScanCallback resolveScanCallback) {
- // Organize the PwoMetadata objects into a map
- final Map urlToPwoMetadata = new HashMap<>();
- for (PwoMetadata pwoMetadata : pwoMetadataCollection) {
- urlToPwoMetadata.put(pwoMetadata.url, pwoMetadata);
- }
-
- // Create the json request object
- JSONObject jsonObj = createUrlMetadataRequestObject(pwoMetadataCollection);
- final long creationTimestamp = new Date().getTime();
- Response.Listener responseListener = new Response.Listener() {
- // Called when the server returns a response
- @Override
- public void onResponse(JSONObject jsonResponse) {
-
- // Build the metadata from the response
- JSONArray foundMetadata;
- try {
- foundMetadata = jsonResponse.getJSONArray("metadata");
- } catch (JSONException e) {
- Log.i(TAG, "Pws gave bad JSON: " + e);
- return;
- }
-
- // Loop through the metadata for each url
- for (int i = 0; i < foundMetadata.length(); i++) {
- UrlMetadata urlMetadata = new UrlMetadata();
- try {
- JSONObject jsonUrlMetadata = foundMetadata.getJSONObject(i);
- urlMetadata.id = jsonUrlMetadata.getString("id");
- urlMetadata.siteUrl = jsonUrlMetadata.getString("url");
- urlMetadata.displayUrl = jsonUrlMetadata.getString("displayUrl");
- urlMetadata.title = jsonUrlMetadata.optString("title");
- urlMetadata.description = jsonUrlMetadata.optString("description");
- urlMetadata.iconUrl = jsonUrlMetadata.optString("icon");
- urlMetadata.rank = jsonUrlMetadata.getDouble("rank");
- urlMetadata.groupid = jsonUrlMetadata.optString("groupid");
- } catch (JSONException e) {
- Log.i(TAG, "Pws gave bad JSON: " + e);
- continue;
- }
-
- PwoMetadata pwoMetadata = urlToPwoMetadata.get(urlMetadata.id);
- urlToPwoMetadata.remove(urlMetadata.id);
- pwoMetadata.setUrlMetadata(urlMetadata,
- new Date().getTime() - creationTimestamp);
- resolveScanCallback.onUrlMetadataReceived(pwoMetadata);
- }
-
- // See which urls the PWS didn't give as a response for
- for (PwoMetadata pwoMetadata : urlToPwoMetadata.values()) {
- resolveScanCallback.onUrlMetadataAbsent(pwoMetadata);
- }
- }
- };
-
- Response.ErrorListener responseErrorListener = new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError volleyError) {
- Log.i(TAG, "VolleyError: " + volleyError.toString());
- }
- };
-
- return new JsonObjectRequest(constructUrlStr(RESOLVE_SCAN_PATH), jsonObj, responseListener,
- responseErrorListener);
- }
-
- /**
- * Asynchronously download the image for the url favicon.
- *
- * @param pwoMetadata contains the relevant UrlMetadata
- */
- public void downloadIcon(final PwoMetadata pwoMetadata,
- final DownloadIconCallback downloadIconCallback) {
- final UrlMetadata urlMetadata = pwoMetadata.urlMetadata;
- Response.Listener responseListener = new Response.Listener() {
- @Override
- public void onResponse(Bitmap response) {
- urlMetadata.icon = response;
- downloadIconCallback.onUrlMetadataIconReceived(pwoMetadata);
- }
- };
- Response.ErrorListener errorListener = new Response.ErrorListener() {
- @Override
- public void onErrorResponse(VolleyError error) {
- downloadIconCallback.onUrlMetadataIconError(pwoMetadata);
- }
- };
- ImageRequest imageRequest = new ImageRequest(urlMetadata.iconUrl, responseListener, 0, 0, null,
- errorListener);
- mRequestQueue.add(imageRequest);
- }
-
- public void cancelAllRequests(final String tag) {
- mRequestQueue.cancelAll(tag);
- }
-
- public void useProdEndpoint() {
- mEndpointUrl = PROD_URL;
- }
-
- public void useDevEndpoint() {
- mEndpointUrl = DEV_URL;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ScreenListenerService.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ScreenListenerService.java
deleted file mode 100644
index 2c37876a..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ScreenListenerService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2015 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 org.physical_web.physicalweb;
-
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IBinder;
-
-/**
- * This is a service registers a broadcast receiver to listen for screen on/off events.
- * It is a very unfortunate service that must exist because we can't register for screen on/off
- * in the manifest.
- */
-
-public class ScreenListenerService extends Service {
- private BroadcastReceiver mScreenStateBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Intent discoveryIntent = new Intent(context, PwoDiscoveryService.class);
- if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
- context.startService(discoveryIntent);
- } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- context.stopService(discoveryIntent);
- }
- }
- };
-
- @Override
- public void onCreate() {
- super.onCreate();
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
- registerReceiver(mScreenStateBroadcastReceiver, intentFilter);
- }
-
- @Override
- public void onDestroy() {
- unregisterReceiver(mScreenStateBroadcastReceiver);
- super.onDestroy();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // Nothing should bind to this service
- return null;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SsdpPwoDiscoverer.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SsdpPwoDiscoverer.java
deleted file mode 100644
index 97a2c11a..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SsdpPwoDiscoverer.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import org.physical_web.physicalweb.ssdp.Ssdp;
-import org.physical_web.physicalweb.ssdp.SsdpMessage;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * This class discovers Physical Web URI/URLs over SSDP.
- */
-
-public class SsdpPwoDiscoverer extends PwoDiscoverer implements Ssdp.SsdpCallback {
- private static final String TAG = "SsdpPwoDiscoverer";
- private static final String PHYSICAL_WEB_SSDP_TYPE = "urn:physical-web-org:device:Basic:1";
- private Context mContext;
- private Thread mThread;
- private Ssdp mSsdp;
-
- public SsdpPwoDiscoverer(Context context) {
- mContext = context;
- }
-
- public void startScanImpl() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- // TODO(?): set timeout using getSsdp().start(NearbyBeaconsFragment.SCAN_TIME_MILLIS)
- // to ensure that SSDP scan thread is stopped automatically after timeout.
- // In this case there is no need to call stop().
- getSsdp().start(null);
- Thread.sleep(200);
- getSsdp().search(PHYSICAL_WEB_SSDP_TYPE);
- } catch (Exception e) {
- Log.e(TAG, e.getMessage(), e);
- }
- }
- }).start();
- }
-
- public void stopScanImpl() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- getSsdp().stop();
- } catch (IOException e) {
- Log.e(TAG, e.getMessage(), e);
- }
- }
- }).start();
- }
-
- public synchronized Ssdp getSsdp() throws IOException {
- if (mSsdp == null) {
- mSsdp = new Ssdp(this);
- }
- return mSsdp;
- }
-
- @Override
- public void onSsdpMessageReceived(SsdpMessage ssdpMessage) {
- final String url = ssdpMessage.get("LOCATION");
- final String st = ssdpMessage.get("ST");
- if (url != null && PHYSICAL_WEB_SSDP_TYPE.equals(st)) {
- Log.d(TAG, "SSDP url received: " + url);
- new Thread(new Runnable() {
- @Override
- public void run() {
- PwoMetadata pwoMetadata = createPwoMetadata(url);
- pwoMetadata.isPublic = false;
- reportPwo(pwoMetadata);
- }
- }).start();
- }
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SwipeRefreshWidget.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SwipeRefreshWidget.java
deleted file mode 100644
index c543a492..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/SwipeRefreshWidget.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ListView;
-
-/**
- * Subclass of {@link android.support.v4.widget.SwipeRefreshLayout} that supports containing a
- * ViewGroup whose first child is a ListView. The ViewGroup can contain other views.
- */
-public class SwipeRefreshWidget extends android.support.v4.widget.SwipeRefreshLayout {
-
- public SwipeRefreshWidget(Context context) {
- super(context);
- }
-
- public SwipeRefreshWidget(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- }
-
- @Override
- public boolean canChildScrollUp() {
- // The real child maps cares about is the list, so check if that can scroll.
- ListView target = (ListView) findViewById(android.R.id.list);
- return target.getChildCount() > 0
- && (target.getFirstVisiblePosition() > 0
- || target.getChildAt(0).getTop() < target.getPaddingTop());
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/BluetoothUuid.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/BluetoothUuid.java
deleted file mode 100644
index 7636c486..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/BluetoothUuid.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-// THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
-// THIS CODE SHOULD FOLLOW ANDROID STYLE.
-
-package org.physical_web.physicalweb.ble;
-
-import android.os.ParcelUuid;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.UUID;
-
-/**
- * Static helper methods and constants to decode the ParcelUuid of remote devices.
- *
- * @hide
- */
-public final class BluetoothUuid {
- /*
- * See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs for the
- * various services. The following 128 bit values are calculated as: uuid * 2^96 + BASE_UUID
- */
- public static final ParcelUuid AudioSink =
- ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid AudioSource =
- ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid AdvAudioDist =
- ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid HSP =
- ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid HSP_AG =
- ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid Handsfree =
- ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid Handsfree_AG =
- ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid AvrcpController =
- ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid AvrcpTarget =
- ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid ObexObjectPush =
- ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
- public static final ParcelUuid Hid =
- ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
- public static final ParcelUuid Hogp =
- ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb");
- public static final ParcelUuid PANU =
- ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid NAP =
- ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid BNEP =
- ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid PBAP_PSE =
- ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid MAP =
- ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid MNS =
- ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid MAS =
- ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
- public static final ParcelUuid BASE_UUID =
- ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
- /** Length of bytes for 16 bit UUID */
- public static final int UUID_BYTES_16_BIT = 2;
- /** Length of bytes for 32 bit UUID */
- public static final int UUID_BYTES_32_BIT = 4;
- /** Length of bytes for 128 bit UUID */
- public static final int UUID_BYTES_128_BIT = 16;
- public static final ParcelUuid[] RESERVED_UUIDS = {
- AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
- ObexObjectPush, PANU, NAP, MAP, MNS, MAS };
-
- public static boolean isAudioSource(ParcelUuid uuid) {
- return uuid.equals(AudioSource);
- }
-
- public static boolean isAudioSink(ParcelUuid uuid) {
- return uuid.equals(AudioSink);
- }
-
- public static boolean isAdvAudioDist(ParcelUuid uuid) {
- return uuid.equals(AdvAudioDist);
- }
-
- public static boolean isHandsfree(ParcelUuid uuid) {
- return uuid.equals(Handsfree);
- }
-
- public static boolean isHeadset(ParcelUuid uuid) {
- return uuid.equals(HSP);
- }
-
- public static boolean isAvrcpController(ParcelUuid uuid) {
- return uuid.equals(AvrcpController);
- }
-
- public static boolean isAvrcpTarget(ParcelUuid uuid) {
- return uuid.equals(AvrcpTarget);
- }
-
- public static boolean isInputDevice(ParcelUuid uuid) {
- return uuid.equals(Hid);
- }
-
- public static boolean isPanu(ParcelUuid uuid) {
- return uuid.equals(PANU);
- }
-
- public static boolean isNap(ParcelUuid uuid) {
- return uuid.equals(NAP);
- }
-
- public static boolean isBnep(ParcelUuid uuid) {
- return uuid.equals(BNEP);
- }
-
- public static boolean isMap(ParcelUuid uuid) {
- return uuid.equals(MAP);
- }
-
- public static boolean isMns(ParcelUuid uuid) {
- return uuid.equals(MNS);
- }
-
- public static boolean isMas(ParcelUuid uuid) {
- return uuid.equals(MAS);
- }
-
- /**
- * Returns true if ParcelUuid is present in uuidArray
- *
- * @param uuidArray - Array of ParcelUuids
- * @param uuid
- */
- public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) {
- if ((uuidArray == null || uuidArray.length == 0) && uuid == null) {
- return true;
- }
- if (uuidArray == null) {
- return false;
- }
- for (ParcelUuid element : uuidArray) {
- if (element.equals(uuid)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if there any common ParcelUuids in uuidA and uuidB.
- *
- * @param uuidA - List of ParcelUuids
- * @param uuidB - List of ParcelUuids
- */
- public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
- if (uuidA == null && uuidB == null) {
- return true;
- }
- if (uuidA == null) {
- return uuidB.length == 0 ? true : false;
- }
- if (uuidB == null) {
- return uuidA.length == 0 ? true : false;
- }
- HashSet uuidSet = new HashSet(Arrays.asList(uuidA));
- for (ParcelUuid uuid : uuidB) {
- if (uuidSet.contains(uuid)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if all the ParcelUuids in ParcelUuidB are present in ParcelUuidA
- *
- * @param uuidA - Array of ParcelUuidsA
- * @param uuidB - Array of ParcelUuidsB
- */
- public static boolean containsAllUuids(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
- if (uuidA == null && uuidB == null) {
- return true;
- }
- if (uuidA == null) {
- return uuidB.length == 0 ? true : false;
- }
- if (uuidB == null) {
- return true;
- }
- HashSet uuidSet = new HashSet(Arrays.asList(uuidA));
- for (ParcelUuid uuid : uuidB) {
- if (!uuidSet.contains(uuid)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Extract the Service Identifier or the actual uuid from the Parcel Uuid. For example, if
- * 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid, this function will return 110B
- *
- * @param parcelUuid
- * @return the service identifier.
- */
- public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
- UUID uuid = parcelUuid.getUuid();
- long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
- return (int) value;
- }
-
- /**
- * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
- * but the returned UUID is always in 128-bit format. Note UUID is little endian in Bluetooth.
- *
- * @param uuidBytes Byte representation of uuid.
- * @return {@link ParcelUuid} parsed from bytes.
- * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed.
- */
- public static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
- if (uuidBytes == null) {
- throw new IllegalArgumentException("uuidBytes cannot be null");
- }
- int length = uuidBytes.length;
- if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT &&
- length != UUID_BYTES_128_BIT) {
- throw new IllegalArgumentException("uuidBytes length invalid - " + length);
- }
- // Construct a 128 bit UUID.
- if (length == UUID_BYTES_128_BIT) {
- ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
- long msb = buf.getLong(8);
- long lsb = buf.getLong(0);
- return new ParcelUuid(new UUID(msb, lsb));
- }
- // For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
- // 128_bit_value = uuid * 2^96 + BASE_UUID
- long shortUuid;
- if (length == UUID_BYTES_16_BIT) {
- shortUuid = uuidBytes[0] & 0xFF;
- shortUuid += (uuidBytes[1] & 0xFF) << 8;
- } else {
- shortUuid = uuidBytes[0] & 0xFF;
- shortUuid += (uuidBytes[1] & 0xFF) << 8;
- shortUuid += (uuidBytes[2] & 0xFF) << 16;
- shortUuid += (uuidBytes[3] & 0xFF) << 24;
- }
- long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
- long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
- return new ParcelUuid(new UUID(msb, lsb));
- }
-
- /**
- * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
- *
- * @param parcelUuid
- * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
- */
- public static boolean is16BitUuid(ParcelUuid parcelUuid) {
- UUID uuid = parcelUuid.getUuid();
- if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
- return false;
- }
- return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
- }
-
- /**
- * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid.
- *
- * @param parcelUuid
- * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise.
- */
- public static boolean is32BitUuid(ParcelUuid parcelUuid) {
- UUID uuid = parcelUuid.getUuid();
- if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
- return false;
- }
- if (is16BitUuid(parcelUuid)) {
- return false;
- }
- return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/ScanRecord.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/ScanRecord.java
deleted file mode 100644
index 0e75ae7b..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/ScanRecord.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-// THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
-// THIS CODE SHOULD FOLLOW ANDROID STYLE.
-//
-// Changes:
-// Replace ArrayMap (new in Android L) with HashMap
-
-package org.physical_web.physicalweb.ble;
-
-import android.os.ParcelUuid;
-import android.support.annotation.Nullable;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Represents a scan record from Bluetooth LE scan.
- */
-public final class ScanRecord {
-
- // The following data type values are assigned by Bluetooth SIG.
- // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
- private static final int DATA_TYPE_FLAGS = 0x01;
- private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
- private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
- private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
- private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
- private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
- private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
- private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
- private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
- private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
- private static final int DATA_TYPE_SERVICE_DATA = 0x16;
- private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
-
- // Flags of the advertising data.
- private final int mAdvertiseFlags;
-
- @Nullable
- private final List mServiceUuids;
-
- private final SparseArray mManufacturerSpecificData;
-
- private final Map mServiceData;
-
- // Transmission power level(in dB).
- private final int mTxPowerLevel;
-
- // Local name of the Bluetooth LE device.
- private final String mDeviceName;
-
- // Raw bytes of scan record.
- private final byte[] mBytes;
-
- /**
- * Returns the advertising flags indicating the discoverable mode and capability of the device.
- * Returns -1 if the flag field is not set.
- */
- public int getAdvertiseFlags() {
- return mAdvertiseFlags;
- }
-
- /**
- * Returns a list of service UUIDs within the advertisement that are used to identify the
- * bluetooth GATT services.
- */
- public List getServiceUuids() {
- return mServiceUuids;
- }
-
- /**
- * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
- * data.
- */
- public SparseArray getManufacturerSpecificData() {
- return mManufacturerSpecificData;
- }
-
- /**
- * Returns the manufacturer specific data associated with the manufacturer id. Returns
- * {@code null} if the {@code manufacturerId} is not found.
- */
- @Nullable
- public byte[] getManufacturerSpecificData(int manufacturerId) {
- return mManufacturerSpecificData.get(manufacturerId);
- }
-
- /**
- * Returns a map of service UUID and its corresponding service data.
- */
- public Map getServiceData() {
- return mServiceData;
- }
-
- /**
- * Returns the service data byte array associated with the {@code serviceUuid}. Returns
- * {@code null} if the {@code serviceDataUuid} is not found.
- */
- @Nullable
- public byte[] getServiceData(ParcelUuid serviceDataUuid) {
- if (serviceDataUuid == null) {
- return null;
- }
- return mServiceData.get(serviceDataUuid);
- }
-
- /**
- * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
- * if the field is not set. This value can be used to calculate the path loss of a received
- * packet using the following equation:
- *
- * pathloss = txPowerLevel - rssi
- */
- public int getTxPowerLevel() {
- return mTxPowerLevel;
- }
-
- /**
- * Returns the local name of the BLE device. The is a UTF-8 encoded string.
- */
- @Nullable
- public String getDeviceName() {
- return mDeviceName;
- }
-
- /**
- * Returns raw bytes of scan record.
- */
- public byte[] getBytes() {
- return mBytes;
- }
-
- private ScanRecord(List serviceUuids,
- SparseArray manufacturerData,
- Map serviceData,
- int advertiseFlags, int txPowerLevel,
- String localName, byte[] bytes) {
- mServiceUuids = serviceUuids;
- mManufacturerSpecificData = manufacturerData;
- mServiceData = serviceData;
- mDeviceName = localName;
- mAdvertiseFlags = advertiseFlags;
- mTxPowerLevel = txPowerLevel;
- mBytes = bytes;
- }
-
- /**
- * Parse scan record bytes to {@link ScanRecord}.
- *
- * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
- *
- * All numerical multi-byte entities and values shall use little-endian byte
- * order.
- *
- * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
- * @hide
- */
- public static ScanRecord parseFromBytes(byte[] scanRecord) {
- if (scanRecord == null) {
- return null;
- }
-
- int currentPos = 0;
- int advertiseFlag = -1;
- List serviceUuids = new ArrayList();
- String localName = null;
- int txPowerLevel = Integer.MIN_VALUE;
-
- SparseArray manufacturerData = new SparseArray();
- Map serviceData = new HashMap();
-
- try {
- while (currentPos < scanRecord.length) {
- // length is unsigned int.
- int length = scanRecord[currentPos++] & 0xFF;
- if (length == 0) {
- break;
- }
- // Note the length includes the length of the field type itself.
- int dataLength = length - 1;
- // fieldType is unsigned int.
- int fieldType = scanRecord[currentPos++] & 0xFF;
- switch (fieldType) {
- case DATA_TYPE_FLAGS:
- advertiseFlag = scanRecord[currentPos] & 0xFF;
- break;
- case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos,
- dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
- break;
- case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos, dataLength,
- BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
- break;
- case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos, dataLength,
- BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
- break;
- case DATA_TYPE_LOCAL_NAME_SHORT:
- case DATA_TYPE_LOCAL_NAME_COMPLETE:
- localName = new String(
- extractBytes(scanRecord, currentPos, dataLength));
- break;
- case DATA_TYPE_TX_POWER_LEVEL:
- txPowerLevel = scanRecord[currentPos];
- break;
- case DATA_TYPE_SERVICE_DATA:
- // The first two bytes of the service data are service data UUID in little
- // endian. The rest bytes are service data.
- int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
- byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
- serviceUuidLength);
- ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
- serviceDataUuidBytes);
- byte[] serviceDataArray = extractBytes(scanRecord,
- currentPos + serviceUuidLength, dataLength - serviceUuidLength);
- serviceData.put(serviceDataUuid, serviceDataArray);
- break;
- case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
- // The first two bytes of the manufacturer specific data are
- // manufacturer ids in little endian.
- int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8)
- + (scanRecord[currentPos] & 0xFF);
- byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
- dataLength - 2);
- manufacturerData.put(manufacturerId, manufacturerDataBytes);
- break;
- default:
- // Just ignore, we don't handle such data type.
- break;
- }
- currentPos += dataLength;
- }
-
- if (serviceUuids.isEmpty()) {
- serviceUuids = null;
- }
- return new ScanRecord(serviceUuids, manufacturerData, serviceData,
- advertiseFlag, txPowerLevel, localName, scanRecord);
- } catch (Exception e) {
- // As the record is invalid, ignore all the parsed results for this packet
- // and return an empty record with raw scanRecord bytes in results
- return new ScanRecord(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
- }
- }
-
- @Override
- public String toString() {
- return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
- + ", mManufacturerSpecificData=" + Utils.toString(mManufacturerSpecificData)
- + ", mServiceData=" + Utils.toString(mServiceData)
- + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
- }
-
- // Parse service UUIDs.
- private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
- int uuidLength, List serviceUuids) {
- while (dataLength > 0) {
- byte[] uuidBytes = extractBytes(scanRecord, currentPos,
- uuidLength);
- serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
- dataLength -= uuidLength;
- currentPos += uuidLength;
- }
- return currentPos;
- }
-
- // Helper method to extract bytes from byte array.
- private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
- byte[] bytes = new byte[length];
- System.arraycopy(scanRecord, start, bytes, 0, length);
- return bytes;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/UriBeacon.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/UriBeacon.java
deleted file mode 100644
index d3e2897f..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/UriBeacon.java
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb.ble;
-
-import android.os.ParcelUuid;
-import android.util.Log;
-import android.util.SparseArray;
-import android.webkit.URLUtil;
-
-import java.net.URISyntaxException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.UUID;
-
-/**
- * Represents a Uri Beacon from Bluetooth LE scan.
- */
-
-public class UriBeacon {
-
- /**
- * The Service Data UUID is the reserved 16-bit Service Data Code in the form of a globally unique
- * 128-bit UUID.
- */
- public static final ParcelUuid URI_SERVICE_UUID =
- ParcelUuid.fromString("0000FED8-0000-1000-8000-00805F9B34FB");
- public static final byte NO_TX_POWER_LEVEL = -101;
- public static final byte NO_FLAGS = 0;
- public static final String NO_URI = "";
- private static final String TAG = "UriBeacon";
- private static final int DATA_TYPE_SERVICE_DATA = 0x16;
- private static final byte[] URI_SERVICE_16_BIT_UUID_BYTES = {(byte) 0xd8, (byte) 0xfe};
-
- //TODO: Add comments
- public static final ParcelUuid TEST_SERVICE_UUID =
- ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
- private static final byte[] TEST_SERVICE_16_BIT_UUID_BYTES = { (byte) 0xaa, (byte) 0xfe};
- private static final byte TEST_URL_FRAME_TYPE = 0x10;
-
- /**
- * URI Scheme maps a byte code into the scheme and an optional scheme specific prefix.
- */
- private static final SparseArray URI_SCHEMES = new SparseArray() {{
- put((byte) 0, "http://www.");
- put((byte) 1, "https://www.");
- put((byte) 2, "http://");
- put((byte) 3, "https://");
- put((byte) 4, "urn:uuid:"); // RFC 2141 and RFC 4122};
- }};
- /**
- * Expansion strings for "http" and "https" schemes. These contain strings appearing anywhere in a
- * URL. Restricted to Generic TLDs. Note: this is a scheme specific encoding.
- */
- private static final SparseArray URL_CODES = new SparseArray() {{
- put((byte) 0, ".com/");
- put((byte) 1, ".org/");
- put((byte) 2, ".edu/");
- put((byte) 3, ".net/");
- put((byte) 4, ".info/");
- put((byte) 5, ".biz/");
- put((byte) 6, ".gov/");
- put((byte) 7, ".com");
- put((byte) 8, ".org");
- put((byte) 9, ".edu");
- put((byte) 10, ".net");
- put((byte) 11, ".info");
- put((byte) 12, ".biz");
- put((byte) 13, ".gov");
- }};
- private static final int FLAGS_FIELD_SIZE = 3;
- private static final int URI_SERVICE_FLAGS_TXPOWER_SIZE = 2;
- private static final byte[] URI_SERVICE_UUID_FIELD = {(byte) 0x03, (byte) 0x03, (byte) 0xD8,
- (byte) 0xFE};
- private static final byte[] URI_SERVICE_DATA_FIELD_HEADER = {0x16, (byte) 0xD8, (byte) 0xFE};
- private static final int MAX_ADVERTISING_DATA_BYTES = 31;
- private static final int MAX_URI_LENGTH = 18;
- private final byte mFlags;
- private final byte mTxPowerLevel;
- private final String mUriString;
-
- // Copy constructor
- UriBeacon(UriBeacon uriBeacon) {
- mUriString = uriBeacon.getUriString();
- mFlags = uriBeacon.getFlags();
- mTxPowerLevel = uriBeacon.getTxPowerLevel();
- }
-
- /**
- * Creates the Uri string with embedded expansion codes.
- *
- * @param uri to be encoded
- * @return the Uri string with expansion codes.
- */
- public static byte[] encodeUri(String uri) {
- if (uri.length() == 0) {
- return new byte[0];
- }
- ByteBuffer bb = ByteBuffer.allocate(uri.length());
- // UUIDs are ordered as byte array, which means most significant first
- bb.order(ByteOrder.BIG_ENDIAN);
- int position = 0;
-
- // Add the byte code for the scheme or return null if none
- Byte schemeCode = encodeUriScheme(uri);
- if (schemeCode == null) {
- return null;
- }
- String scheme = URI_SCHEMES.get(schemeCode);
- bb.put(schemeCode);
- position += scheme.length();
-
- if (URLUtil.isNetworkUrl(scheme)) {
- return encodeUrl(uri, position, bb);
- } else if ("urn:uuid:".equals(scheme)) {
- return encodeUrnUuid(uri, position, bb);
- }
- return null;
- }
-
- /**
- * @return The Uri that will be broadcasted in a byte[]
- */
- public byte[] getUriBytes() {
- return encodeUri(mUriString);
- }
-
- /**
- * @return the Uri flags indicating the discoverable mode and capability of the device.
- */
- public byte getFlags() {
- return mFlags;
- }
-
- /**
- * @return the transmission power level of the packet in dBm. This value can be used to calculate
- * the path loss of a received packet using the following equation: path loss =
- * txPowerLevel - RSSI
- */
- public byte getTxPowerLevel() {
- return mTxPowerLevel;
- }
-
- /**
- * @return the Uri text of the packet.
- */
- public String getUriString() {
- return mUriString;
- }
-
- /**
- * Parse scan record bytes to {@link UriBeacon}. The format is defined in Uri Beacon
- * Definition.
- *
- * @param scanRecordBytes The scan record of Bluetooth LE advertisement and/or scan response.
- */
- public static UriBeacon parseFromBytes(byte[] scanRecordBytes) {
- byte[] serviceData = parseServiceDataFromBytes(scanRecordBytes);
- // Minimum UriBeacon consists of flags, TxPower
- if (serviceData != null && serviceData.length >= 2) {
- int currentPos = 0;
- byte flags = serviceData[currentPos++];
- byte txPowerLevel = serviceData[currentPos++];
- String uri = decodeUri(serviceData, currentPos);
- return new UriBeacon(flags, txPowerLevel, uri);
- }
- //TODO: refactor
- serviceData = parseTestServiceDataFromBytes(scanRecordBytes);
- if (serviceData != null && serviceData.length >= 2) {
- int currentPos = 0;
- byte txPowerLevel = serviceData[currentPos++];
- byte flags = (byte) (serviceData[currentPos] >> 4);
- serviceData[currentPos] = (byte) (serviceData[currentPos] & 0xFF);
- String uri = decodeUri(serviceData, currentPos);
- return new UriBeacon(flags, txPowerLevel, uri);
- }
- return null;
- }
-
- @Override
- public String toString() {
- return String.format(Locale.ENGLISH,
- "%s@(uri:'%s' txPowerLevel:%d flags:%d)",
- getClass().getSimpleName(), mUriString, mTxPowerLevel, mFlags);
- }
-
- /**
- * The advertisement data for the UriBeacon as a byte array.
- *
- * @return the UriBeacon bytes
- */
- public byte[] toByteArray() {
- int totalUriBytes = totalBytes(mUriString);
- if (totalUriBytes == 0) {
- return null;
- }
- ByteBuffer buffer = ByteBuffer.allocateDirect(totalUriBytes);
- buffer.put(URI_SERVICE_UUID_FIELD);
- byte[] uriBytes;
- uriBytes = encodeUri(mUriString);
- byte length = (byte) (URI_SERVICE_DATA_FIELD_HEADER.length +
- URI_SERVICE_FLAGS_TXPOWER_SIZE + uriBytes.length);
- buffer.put(length);
- buffer.put(URI_SERVICE_DATA_FIELD_HEADER);
- buffer.put(mFlags);
- buffer.put(mTxPowerLevel);
- buffer.put(uriBytes);
- return byteBufferToArray(buffer);
- }
-
- /**
- *
- * @param uriString
- * @return
- */
- public static int uriLength(String uriString) {
- byte[] encodedUri = encodeUri(uriString);
- if (encodedUri == null) {
- return -1;
- } else {
- return encodedUri.length;
- }
- }
-
- public static class Builder {
-
- private String mUriString;
- private byte[] mUriBytes;
- private byte mFlags = NO_FLAGS;
- private byte mTxPowerLevel = NO_TX_POWER_LEVEL;
-
- /**
- * Add a Uri to the UriBeacon advertised data.
- *
- * @param uriString The Uri to be advertised.
- * @return The UriBeacon Builder.
- */
- public Builder uriString(String uriString) {
- mUriString = uriString;
- return this;
- }
-
- public Builder uriString(byte[] uriBytes) {
- mUriBytes = uriBytes;
- return this;
- }
-
- /**
- * Add flags to the UriBeacon advertised data.
- *
- * @param flags The flags to be advertised.
- * @return The UriBeacon Builder.
- */
- public Builder flags(byte flags) {
- mFlags = flags;
- return this;
- }
-
- /**
- * Add a Tx Power Level to the UriBeacon advertised data.
- *
- * @param txPowerLevel The TX Power Level to be advertised.
- * @return The UriBeacon Builder.
- */
- public Builder txPowerLevel(byte txPowerLevel) {
- mTxPowerLevel = txPowerLevel;
- return this;
- }
-
- /**
- * Build the beacon.
- *
- * @return The UriBeacon
- * @throws URISyntaxException if the uri provided is not valid
- */
- public UriBeacon build() throws URISyntaxException {
- if (mUriBytes != null) {
- mUriString = decodeUri(mUriBytes, 0);
- if (mUriString == null) {
- throw new IllegalArgumentException("Could not decode URI");
- }
- }
-
- if (mUriString == null) {
- throw new IllegalArgumentException(
- "UriBeacon advertisements must include a URI");
- }
- // The firmware adds ADV Flags taking up 3 bytes so we can only send 31 - 3 = 28 bytes.
- // The Service UUID is 4 bytes. The Service Data contains a header (4 bytes),
- // Flags (1 byte) and TX Power Level (1 byte).
- // So this works out to 28 - 4 - 4 - 1 - 1 = 18 bytes for Service Data Uri.
- int length = uriLength(mUriString);
- if (length > MAX_URI_LENGTH) {
- throw new URISyntaxException(mUriString, "Uri size is larger than "
- + MAX_URI_LENGTH + " bytes");
- } else if (length == -1) {
- throw new URISyntaxException(mUriString, "Not a valid URI");
- }
- return new UriBeacon(mFlags, mTxPowerLevel, mUriString);
- }
- }
-
- private UriBeacon(byte flags, byte txPowerLevel, String uriString) {
- mFlags = flags;
- mTxPowerLevel = txPowerLevel;
- mUriString = uriString;
- }
-
- private static String decodeUri(byte[] serviceData, int offset) {
- if (serviceData.length == offset) {
- return NO_URI;
- }
- StringBuilder uriBuilder = new StringBuilder();
- if (offset < serviceData.length) {
- byte b = serviceData[offset++];
- String scheme = URI_SCHEMES.get(b);
- if (scheme != null) {
- uriBuilder.append(scheme);
- if (URLUtil.isNetworkUrl(scheme)) {
- return decodeUrl(serviceData, offset, uriBuilder);
- } else if ("urn:uuid:".equals(scheme)) {
- return decodeUrnUuid(serviceData, offset, uriBuilder);
- }
- }
- Log.w(TAG, "decodeUri unknown Uri scheme code=" + b);
- }
- return null;
- }
-
- private static String decodeUrl(byte[] serviceData, int offset, StringBuilder urlBuilder) {
- while (offset < serviceData.length) {
- byte b = serviceData[offset++];
- String code = URL_CODES.get(b);
- if (code != null) {
- urlBuilder.append(code);
- } else {
- urlBuilder.append((char) b);
- }
- }
- return urlBuilder.toString();
- }
-
- private static String decodeUrnUuid(byte[] serviceData, int offset, StringBuilder urnBuilder) {
- ByteBuffer bb = ByteBuffer.wrap(serviceData);
- // UUIDs are ordered as byte array, which means most significant first
- bb.order(ByteOrder.BIG_ENDIAN);
- long mostSignificantBytes, leastSignificantBytes;
- try {
- bb.position(offset);
- mostSignificantBytes = bb.getLong();
- leastSignificantBytes = bb.getLong();
- } catch (BufferUnderflowException e) {
- Log.w(TAG, "decodeUrnUuid BufferUnderflowException!");
- return null;
- }
- UUID uuid = new UUID(mostSignificantBytes, leastSignificantBytes);
- urnBuilder.append(uuid.toString());
- return urnBuilder.toString();
- }
-
- /**
- * Finds the longest expansion from the uri at the current position.
- *
- * @param uriString the Uri
- * @param pos start position
- * @return an index in URI_MAP or 0 if none.
- */
- private static byte findLongestExpansion(String uriString, int pos) {
- byte expansion = -1;
- int expansionLength = 0;
- for (int i = 0; i < URL_CODES.size(); i++) {
- // get the key and value.
- int key = URL_CODES.keyAt(i);
- String value = URL_CODES.valueAt(i);
- if (value.length() > expansionLength && uriString.startsWith(value, pos)) {
- expansion = (byte) key;
- expansionLength = value.length();
- }
- }
- return expansion;
- }
-
- private static Byte encodeUriScheme(String uri) {
- String lowerCaseUri = uri.toLowerCase(Locale.ENGLISH);
- for (int i = 0; i < URI_SCHEMES.size(); i++) {
- // get the key and value.
- int key = URI_SCHEMES.keyAt(i);
- String value = URI_SCHEMES.valueAt(i);
- if (lowerCaseUri.startsWith(value)) {
- return (byte) key;
- }
- }
- return null;
- }
-
- private static byte[] encodeUrl(String url, int position, ByteBuffer bb) {
- while (position < url.length()) {
- byte expansion = findLongestExpansion(url, position);
- if (expansion >= 0) {
- bb.put(expansion);
- position += URL_CODES.get(expansion).length();
- } else {
- bb.put((byte) url.charAt(position++));
- }
- }
- return byteBufferToArray(bb);
- }
-
- private static byte[] encodeUrnUuid(String urn, int position, ByteBuffer bb) {
- String uuidString = urn.substring(position, urn.length());
- UUID uuid;
- try {
- uuid = UUID.fromString(uuidString);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "encodeUrnUuid invalid urn:uuid format - " + urn);
- return null;
- }
- // UUIDs are ordered as byte array, which means most significant first
- bb.order(ByteOrder.BIG_ENDIAN);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- return byteBufferToArray(bb);
- }
-
- private static byte[] byteBufferToArray(ByteBuffer bb) {
- byte[] bytes = new byte[bb.position()];
- bb.rewind();
- bb.get(bytes, 0, bytes.length);
- return bytes;
- }
-
- private static byte[] UuidToByteArray(UUID uuid) {
- ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- return byteBufferToArray(bb);
- }
-
- // Compute the size of the advertisement data in the Service UUID and Service Data fields.
- // This does not include the ADV Flag Fields (3 bytes).
- private static int totalBytes(String uriString) {
- byte[] encodedUri = encodeUri(uriString);
- if (encodedUri == null) {
- return 0;
- }
- int size = URI_SERVICE_UUID_FIELD.length;
- size += 1; // length is one byte
- size += URI_SERVICE_DATA_FIELD_HEADER.length;
- size += 1; // flags is one byte.
- size += 1; // tx power level value is one byte.
- size += encodedUri.length;
- return size;
- }
-
- /**
- * Return the Service Data for Uri Service.
- *
- * @param scanRecord The scanRecord containing the UriBeacon advertisement.
- * @return data from the Uri Service field
- */
- private static byte[] parseServiceDataFromBytes(byte[] scanRecord) {
- int currentPos = 0;
- try {
- while (currentPos < scanRecord.length) {
- int fieldLength = scanRecord[currentPos++] & 0xff;
- if (fieldLength == 0) {
- break;
- }
- int fieldType = scanRecord[currentPos] & 0xff;
- if (fieldType == DATA_TYPE_SERVICE_DATA) {
- // The first two bytes of the service data are service data UUID.
- if (scanRecord[currentPos + 1] == URI_SERVICE_16_BIT_UUID_BYTES[0]
- && scanRecord[currentPos + 2] == URI_SERVICE_16_BIT_UUID_BYTES[1]) {
- // jump to data
- currentPos += 3;
- // length includes the length of the field type and ID
- byte[] bytes = new byte[fieldLength - 3];
- System.arraycopy(scanRecord, currentPos, bytes, 0, fieldLength - 3);
- return bytes;
- }
- }
- // length includes the length of the field type
- currentPos += fieldLength;
- }
- } catch (Exception e) {
- Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord), e);
- }
- return null;
- }
- private static byte[] parseTestServiceDataFromBytes(byte[] scanRecord) {
- //TODO: Add comments
- int currentPos = 0;
- try {
- while (currentPos < scanRecord.length) {
- int fieldLength = scanRecord[currentPos++] & 0xff;
- if (fieldLength == 0) {
- break;
- }
- int fieldType = scanRecord[currentPos] & 0xff;
- if (fieldType == DATA_TYPE_SERVICE_DATA) {
- if (scanRecord[currentPos + 1] == TEST_SERVICE_16_BIT_UUID_BYTES[0]
- && scanRecord[currentPos + 2] == TEST_SERVICE_16_BIT_UUID_BYTES[1]
- && scanRecord[currentPos + 3] == TEST_URL_FRAME_TYPE) {
- // Jump to beginning of frame.
- currentPos += 4;
- // TODO: Add tests
- // field length - field type - ID - frame type
- byte[] bytes = new byte[fieldLength - 4];
- System.arraycopy(scanRecord, currentPos, bytes, 0, fieldLength - 4);
- return bytes;
- }
- }
- // length includes the length of the field type.
- currentPos += fieldLength;
- }
- } catch (Exception e) {
- Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord), e);
- }
- return null;
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/Utils.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/Utils.java
deleted file mode 100644
index b4aa2733..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ble/Utils.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-// THIS IS MODIFIED COPY OF THE "L" PLATFORM CLASS. BE CAREFUL ABOUT EDITS.
-// THIS CODE SHOULD FOLLOW ANDROID STYLE.
-//
-// Changes:
-// Removed the last two equals() methods.
-
-package org.physical_web.physicalweb.ble;
-
-import android.util.SparseArray;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Helper class for Bluetooth LE utils.
- *
- * @hide
- */
-public class Utils {
-
- /**
- * Returns a string composed from a {@link SparseArray}.
- */
- static String toString(SparseArray array) {
- if (array == null) {
- return "null";
- }
- if (array.size() == 0) {
- return "{}";
- }
- StringBuilder buffer = new StringBuilder();
- buffer.append('{');
- for (int i = 0; i < array.size(); ++i) {
- buffer.append(array.keyAt(i)).append("=").append(array.valueAt(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-
- /**
- * Returns a string composed from a {@link Map}.
- */
- static String toString(Map map) {
- if (map == null) {
- return "null";
- }
- if (map.isEmpty()) {
- return "{}";
- }
- StringBuilder buffer = new StringBuilder();
- buffer.append('{');
- Iterator> it = map.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry entry = it.next();
- Object key = entry.getKey();
- buffer.append(key).append("=").append(Arrays.toString(map.get(key)));
- if (it.hasNext()) {
- buffer.append(", ");
- }
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/Ssdp.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/Ssdp.java
deleted file mode 100644
index 1f79457b..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/Ssdp.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb.ssdp;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.SocketTimeoutException;
-import java.nio.charset.StandardCharsets;
-
-/**
- * This class implements SSDP protocol.
- * It supports discovery of SSDP devices in
- * the local network using SSDP M-SEARCH search requests
- */
-
-public class Ssdp implements Runnable {
- public static final String TAG = "Ssdp";
- public static final String SSDP_ADDRESS = "239.255.255.250";
- public static final int SSDP_PORT = 1900;
- public static final String SSDP_HOST = SSDP_ADDRESS + ":" + SSDP_PORT;
- public static final String MAX_AGE = "max-age=1800";
- public static final int TTL = 128;
- public static final int MX = 3;
- public static final String ALIVE = "ssdp:alive";
- public static final String BYEBYE = "ssdp:byebye";
- public static final String UPDATE = "ssdp:update";
- public static final String DISCOVER = "\"ssdp:discover\"";
- public static final String TYPE_M_SEARCH = "M-SEARCH";
- public static final String TYPE_NOTIFY = "NOTIFY";
- public static final String TYPE_200_OK = "200 OK";
-
- private SsdpCallback mSsdpCallback;
- private SocketAddress mMulticastGroup;
- private DatagramSocket mDatagramSocket;
- private Thread mThread;
-
- public Ssdp(SsdpCallback ssdpCallback) throws IOException {
- mSsdpCallback = ssdpCallback;
- mMulticastGroup = new InetSocketAddress(SSDP_ADDRESS, SSDP_PORT);
- }
-
- public synchronized boolean start(Integer timeout) throws IOException {
- if (mThread == null) {
- // create a DatagramSocket without binding to any address
- mDatagramSocket = new DatagramSocket(null);
- mDatagramSocket.setReuseAddress(true);
- // bind to any free port
- mDatagramSocket.bind(null);
- if (timeout != null && timeout > 0) {
- mDatagramSocket.setSoTimeout(timeout);
- }
- mThread = new Thread(this);
- mThread.start();
- return true;
- }
- return false;
- }
-
- public synchronized boolean stop() throws IOException {
- if (mThread != null) {
- mThread.interrupt();
- mDatagramSocket.close();
- mThread = null;
- mDatagramSocket = null;
- return true;
- }
- return false;
- }
-
- public synchronized void search(SsdpMessage msg) throws IOException {
- if (mDatagramSocket != null){
- byte bytes[] = msg.toString().getBytes(StandardCharsets.UTF_8);
- DatagramPacket dp = new DatagramPacket(bytes, bytes.length, mMulticastGroup);
- mDatagramSocket.send(dp);
- }
- }
-
- public SsdpMessage search(String text) throws IOException {
- SsdpMessage msg = new SsdpMessage(SsdpMessage.TYPE_SEARCH);
- msg.getHeaders().put("ST", text);
- msg.getHeaders().put("HOST", SSDP_HOST);
- msg.getHeaders().put("MAN", DISCOVER);
- msg.getHeaders().put("MX" , MX + "");
- search(msg);
- return msg;
- }
-
- @Override
- public void run() {
- Thread currentThread = Thread.currentThread();
- byte[] buf = new byte[1024];
- Log.d(TAG, "SSDP scan started");
- while (!currentThread.isInterrupted() && mDatagramSocket != null) {
- try {
- DatagramPacket dp = new DatagramPacket(buf, buf.length);
- mDatagramSocket.receive(dp);
- String txt = new String(dp.getData(), StandardCharsets.UTF_8);
- SsdpMessage msg = new SsdpMessage(txt);
- mSsdpCallback.onSsdpMessageReceived(msg);
- } catch (SocketTimeoutException e) {
- Log.d(TAG, e.getMessage());
- break;
- } catch (IOException e) {
- Log.e(TAG, e.getMessage());
- }
- }
- // cleanup if needed
- synchronized (this) {
- if (mThread == currentThread) {
- mThread = null;
- if (mDatagramSocket != null) {
- mDatagramSocket.close();
- mDatagramSocket = null;
- }
- }
- }
- Log.d(TAG, "SSDP scan terminated");
- }
-
- /**
- * Callback for Ssdp discoveries.
- */
- public interface SsdpCallback {
- public void onSsdpMessageReceived(SsdpMessage ssdpMessage);
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/SsdpMessage.java b/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/SsdpMessage.java
deleted file mode 100644
index c2d201ff..00000000
--- a/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/ssdp/SsdpMessage.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2014 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 org.physical_web.physicalweb.ssdp;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * This class is the Java representation of SSDP messages.
- * It creates SsdpMessage instances from a text representation
- * of a SSDP message or converts a SsdpMessage instance to string
- */
-
-public class SsdpMessage {
- public static final int TYPE_SEARCH = 0;
- public static final int TYPE_NOTIFY = 1;
- public static final int TYPE_FOUND = 2;
- private static final String NL = "\r\n";
- private static final String FIRST_LINE[] = {
- Ssdp.TYPE_M_SEARCH + " * HTTP/1.1",
- Ssdp.TYPE_NOTIFY + " * HTTP/1.1",
- "HTTP/1.1 " + Ssdp.TYPE_200_OK
- };
- private int mType;
- private Map mHeaders;
-
- public SsdpMessage(int type) {
- this.mType = type;
- }
-
- public SsdpMessage(String txt) {
- String lines[] = txt.split(NL);
- String line = lines[0].trim();
- if (line.startsWith(Ssdp.TYPE_M_SEARCH)) {
- this.mType = TYPE_SEARCH;
- } else if (line.startsWith(Ssdp.TYPE_NOTIFY)) {
- this.mType = TYPE_NOTIFY;
- } else {
- this.mType = TYPE_FOUND;
- }
- for (int i = 1; i < lines.length; i++) {
- line = lines[i].trim();
- int index = line.indexOf(':');
- if (index > 0) {
- String key = line.substring(0, index).trim();
- String value = line.substring(index + 1).trim();
- getHeaders().put(key, value);
- }
- }
- }
-
- public Map getHeaders() {
- if (mHeaders == null) {
- mHeaders = new HashMap<>();
- }
- return mHeaders;
- }
-
- public int getType() {
- return mType;
- }
-
- public String get(String key) {
- return getHeaders().get(key);
- }
-
- public String put(String key, String value) {
- return getHeaders().put(key, value);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(FIRST_LINE[this.mType]).append(NL);
- for (Map.Entry entry: getHeaders().entrySet()) {
- builder.append(entry.getKey())
- .append(": ")
- .append(entry.getValue())
- .append(NL);
- }
- builder.append(NL);
- return builder.toString();
- }
-}
diff --git a/android/PhysicalWeb/app/src/main/res/anim/fade_in_activity.xml b/android/PhysicalWeb/app/src/main/res/anim/fade_in_activity.xml
deleted file mode 100644
index 820d02cf..00000000
--- a/android/PhysicalWeb/app/src/main/res/anim/fade_in_activity.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up.xml b/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up.xml
deleted file mode 100644
index 45290ee4..00000000
--- a/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up_fragment.xml b/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up_fragment.xml
deleted file mode 100644
index 09e8e2f6..00000000
--- a/android/PhysicalWeb/app/src/main/res/anim/fade_in_and_slide_up_fragment.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/anim/fade_out_fragment.xml b/android/PhysicalWeb/app/src/main/res/anim/fade_out_fragment.xml
deleted file mode 100644
index 29953786..00000000
--- a/android/PhysicalWeb/app/src/main/res/anim/fade_out_fragment.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-hdpi/ic_launcher.png b/android/PhysicalWeb/app/src/main/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index d7be2b0a..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-hdpi/ic_launcher.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-mdpi/ic_launcher.png b/android/PhysicalWeb/app/src/main/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index 1925b8bd..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-mdpi/ic_launcher.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xhdpi/ic_launcher.png b/android/PhysicalWeb/app/src/main/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index defbccbc..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/found_beacon_card.xml b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/found_beacon_card.xml
deleted file mode 100644
index d228e3ed..00000000
--- a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/found_beacon_card.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 0b2d499f..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_notification.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_notification.png
deleted file mode 100644
index be811a86..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_notification.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_0.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_0.png
deleted file mode 100644
index f9b79db7..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_0.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_1.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_1.png
deleted file mode 100644
index d0205ca4..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_1.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_2.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_2.png
deleted file mode 100644
index dee4cb45..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/ic_scanning_2.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/menu_item_background.9.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/menu_item_background.9.png
deleted file mode 100755
index dfc8e732..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/menu_item_background.9.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/scanning_animation.xml b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/scanning_animation.xml
deleted file mode 100644
index 8ffe5144..00000000
--- a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/scanning_animation.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/settings_xxhdpi.png b/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/settings_xxhdpi.png
deleted file mode 100644
index d33e7426..00000000
Binary files a/android/PhysicalWeb/app/src/main/res/drawable-xxhdpi/settings_xxhdpi.png and /dev/null differ
diff --git a/android/PhysicalWeb/app/src/main/res/layout/activity_main.xml b/android/PhysicalWeb/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index cc00494e..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/activity_oob.xml b/android/PhysicalWeb/app/src/main/res/layout/activity_oob.xml
deleted file mode 100644
index 555ddd63..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/activity_oob.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/fragment_about.xml b/android/PhysicalWeb/app/src/main/res/layout/fragment_about.xml
deleted file mode 100644
index 95da5cef..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/fragment_about.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/fragment_beacon_config.xml b/android/PhysicalWeb/app/src/main/res/layout/fragment_beacon_config.xml
deleted file mode 100644
index ac98564b..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/fragment_beacon_config.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/fragment_nearby_beacons.xml b/android/PhysicalWeb/app/src/main/res/layout/fragment_nearby_beacons.xml
deleted file mode 100644
index 5b85b841..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/fragment_nearby_beacons.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/list_item_nearby_beacon.xml b/android/PhysicalWeb/app/src/main/res/layout/list_item_nearby_beacon.xml
deleted file mode 100644
index c3b4d49a..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/list_item_nearby_beacon.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/layout/notification_big_view.xml b/android/PhysicalWeb/app/src/main/res/layout/notification_big_view.xml
deleted file mode 100644
index b7897eb4..00000000
--- a/android/PhysicalWeb/app/src/main/res/layout/notification_big_view.xml
+++ /dev/null
@@ -1,231 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/menu/main.xml b/android/PhysicalWeb/app/src/main/res/menu/main.xml
deleted file mode 100644
index 934cf2a3..00000000
--- a/android/PhysicalWeb/app/src/main/res/menu/main.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
diff --git a/android/PhysicalWeb/app/src/main/res/menu/menu_oob.xml b/android/PhysicalWeb/app/src/main/res/menu/menu_oob.xml
deleted file mode 100644
index 60c33be1..00000000
--- a/android/PhysicalWeb/app/src/main/res/menu/menu_oob.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
diff --git a/android/PhysicalWeb/app/src/main/res/values/colors.xml b/android/PhysicalWeb/app/src/main/res/values/colors.xml
deleted file mode 100644
index ee00fca8..00000000
--- a/android/PhysicalWeb/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- #1C47C1
- #099F39
- #545454
- #4285f4
- #333333
- #5A5A5A
- #FFFFFF
- #70a2d3
- #c2c2c2
-
\ No newline at end of file
diff --git a/android/PhysicalWeb/app/src/main/res/values/strings.xml b/android/PhysicalWeb/app/src/main/res/values/strings.xml
deleted file mode 100644
index ab655eff..00000000
--- a/android/PhysicalWeb/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
- Physical Web
- Edit URLs
- About
- Address
- URL
- SAVE URL
- Version
- favicon for the given URL for this
- entry
- Put your beacon into config mode
- Beacon found!
- Move Beacon closer!
- Saving to Beacon
- URL Saved
- Error Saving URL
- Edit URLs
- Nearby Beacons
- About
- Searching for beacons
- Physical Web
- https://google.github.io/physical-web/mobile/android/getting-started.html
- No beacons found
- VIEW MORE
- Error Reading URL
-
-
- Beacon Nearby
- Beacons Nearby
-
-
- OobActivity
- Settings
- ACCEPT & CONTINUE
- userOptedIn
- physical_web_preferences
-
-
- org.physical_web.physicalweb.DISCOVERY_SERVICE_PREFS
- loading…
- tx:
- rssi:
- distance:
- region:
- rank:
- scan:
- pws:
- group:
- Pull down to see them.
- URL is too long. Please use a shortener.
- Non-ASCII characters unsupported in URL. Please use a shortener.
- Device does not support Bluetooth
-
-
diff --git a/android/PhysicalWeb/app/src/main/res/values/styles.xml b/android/PhysicalWeb/app/src/main/res/values/styles.xml
deleted file mode 100644
index b1a8af00..00000000
--- a/android/PhysicalWeb/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/PhysicalWeb/build.gradle b/android/PhysicalWeb/build.gradle
deleted file mode 100644
index c940b371..00000000
--- a/android/PhysicalWeb/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:1.3.0'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- jcenter()
- }
- gradle.projectsEvaluated {
- tasks.withType(JavaCompile) {
- options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
- }
- }
-}
diff --git a/android/PhysicalWeb/gradle.properties b/android/PhysicalWeb/gradle.properties
deleted file mode 100644
index 5d08ba75..00000000
--- a/android/PhysicalWeb/gradle.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Settings specified in this file will override any Gradle settings
-# configured through the IDE.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
diff --git a/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.jar b/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 8c0fb64a..00000000
Binary files a/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.properties b/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 7bd1b3b4..00000000
--- a/android/PhysicalWeb/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Oct 24 16:22:30 PDT 2014
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/android/PhysicalWeb/gradlew b/android/PhysicalWeb/gradlew
deleted file mode 100755
index 91a7e269..00000000
--- a/android/PhysicalWeb/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/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/android/PhysicalWeb/gradlew.bat b/android/PhysicalWeb/gradlew.bat
deleted file mode 100644
index aec99730..00000000
--- a/android/PhysicalWeb/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@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/android/PhysicalWeb/settings.gradle b/android/PhysicalWeb/settings.gradle
deleted file mode 100644
index ec9832b2..00000000
--- a/android/PhysicalWeb/settings.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-include ':app', ':libs'
-
-project(':libs').projectDir = new File('../../java/libs')
diff --git a/android/README.md b/android/README.md
deleted file mode 100644
index 948ba4b6..00000000
--- a/android/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Android client
-
-This Android client can be downloaded from the [Google Play store](https://play.google.com/store/apps/details?id=physical_web.org.physicalweb). A walkthrough of the app [is here](http://github.com/google/physical-web/blob/master/documentation/android_client_walkthrough.md) and will show you how to put your URL into a beacon.
-
-Keep in mind that this software is just a prototype. We are doing it this way to easily and quickly test the concept. Eventually, the goal is to have this Physical Web code rolled into each browser.
\ No newline at end of file
diff --git a/assets/.sprockets-manifest-d096526b5d4229de59f8b2c6ab56729f.json b/assets/.sprockets-manifest-d096526b5d4229de59f8b2c6ab56729f.json
new file mode 100644
index 00000000..fc8c9ed6
--- /dev/null
+++ b/assets/.sprockets-manifest-d096526b5d4229de59f8b2c6ab56729f.json
@@ -0,0 +1 @@
+{"files":{"html5-boilerplate/dist/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/dist/apple-touch-icon.png","integrity":"sha256-zH12rJxGUoANA1N8leqeKxQTEcQ305oK3asWaccj3CE=","digest":"cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21","size":3959},"html5-boilerplate/dist/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/dist/tile-wide.png","integrity":"sha256-W11ccqeaLFCA201YifKHlQSGxriYAGc/1TLS9ZMS2fs=","digest":"5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb","size":1854},"html5-boilerplate/dist/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/dist/tile.png","integrity":"sha256-gNveSM7FCw/h24c5w1z96NmyBE87Nvzcd21fNFijHF8=","digest":"80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f","size":3482},"html5-boilerplate/src/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/src/apple-touch-icon.png","integrity":"sha256-zH12rJxGUoANA1N8leqeKxQTEcQ305oK3asWaccj3CE=","digest":"cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21","size":3959},"html5-boilerplate/src/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/src/tile-wide.png","integrity":"sha256-W11ccqeaLFCA201YifKHlQSGxriYAGc/1TLS9ZMS2fs=","digest":"5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb","size":1854},"html5-boilerplate/src/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png":{"mtime":"2016-05-17T09:42:22-07:00","logical_path":"html5-boilerplate/src/tile.png","integrity":"sha256-gNveSM7FCw/h24c5w1z96NmyBE87Nvzcd21fNFijHF8=","digest":"80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f","size":3482},"images/02-animation-66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/02-animation.png","integrity":"sha256-Zt5ZPlBrNkcYVMwtGWLHAL13nlfr547TIUVY8T+RBmE=","digest":"66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661","size":8809},"images/03-animation-ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/03-animation.png","integrity":"sha256-zmj3zjdON4GUUo8HP2MwOotv2KRTik39POOvsgho0gk=","digest":"ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209","size":9641},"images/04-animation-35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/04-animation.png","integrity":"sha256-Nd6qKQ6jG58+pI31x7K8t92+FMEiD8Vn513lQunbnLo=","digest":"35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba","size":4298},"images/05-animation-9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/05-animation.png","integrity":"sha256-msLCaPJN2XEaboPvXnhdPqTk6VMEaro3R4rWWNgALD8=","digest":"9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f","size":8191},"images/apple-touch-120x120-8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/apple-touch-120x120.png","integrity":"sha256-i/bbWQ5mg1kVQX3v0G5h3/x+nBNF6QsDIQ3dRvSfUoY=","digest":"8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286","size":2461},"images/apple-touch-152x152-9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/apple-touch-152x152.png","integrity":"sha256-kELpkPDd77vO0dNBuBgmFHGvClDX2tOaMwsnQr8Y6d4=","digest":"9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de","size":3109},"images/apple-touch-60x60-9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/apple-touch-60x60.png","integrity":"sha256-mgF1w3//y3ije/URDavfjcZEbH6sm8GGBn+HyNsiOZY=","digest":"9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996","size":1241},"images/apple-touch-76x76-7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/apple-touch-76x76.png","integrity":"sha256-ejEINCl1ofyMRgZHKbOTj1ejgKn1Jl5nb/rCMHNmDKM=","digest":"7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3","size":1584},"images/beacon-graphic-841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/beacon-graphic.png","integrity":"sha256-hBuqZa/NG/GUL3/BedMyHDI6jx02YlXPnHK/CefOUy4=","digest":"841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e","size":15544},"images/beacon-graphic-0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3.svg":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/beacon-graphic.svg","integrity":"sha256-AhPMIjHLXDQBxO+VN85zMc4wuEau392oQ5XVciQXBOM=","digest":"0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3","size":420045},"images/bg-banner-46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/bg-banner.png","integrity":"sha256-RvIvSt/qdhytLUUjXi3cKq+eC2yh9VLBn7VxG+Gmimk=","digest":"46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69","size":24006},"images/bg-banner-a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c.svg":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/bg-banner.svg","integrity":"sha256-pHfJ94XAYwDbvc5N/u1HgO4sEskCxjPNVPB2ud0hlxw=","digest":"a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c","size":7606},"images/image01-24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image01.png","integrity":"sha256-JDidqi+mOEHDIsl0xTjYBvtwRAjDv5+eSTY6/sj04Ro=","digest":"24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a","size":158049},"images/image01@2x-c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image01@2x.png","integrity":"sha256-yJaSxQtTM+FroCGKAE3H4zps6+0BsQtEMbhLI/wpiTU=","digest":"c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935","size":458109},"images/image02-ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image02.png","integrity":"sha256-rGugqgEYP3TmqEaupxsCH/ao1S3FWY5ftWBfrgGZj+g=","digest":"ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8","size":134572},"images/image03-36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image03.png","integrity":"sha256-NvQKzKpGK1V+ogYQ9SCayq32DZoulZiGoo5BNBPwSjA=","digest":"36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30","size":138149},"images/image04-4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image04.png","integrity":"sha256-Tp9Gcv/6VoAaXbb8yhT6OIo38WqXSmS+okITV0Pk1xQ=","digest":"4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714","size":126136},"images/image05-656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image05.png","integrity":"sha256-ZW2bBdo0eESVmG5jkZ8aeFVyvankyYg9ojxQR3/U3a0=","digest":"656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad","size":125576},"images/image06-8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image06.png","integrity":"sha256-gFeHewefV/zmVf+uCfKzEndy001iMCQBKLTrKZj74vA=","digest":"8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0","size":122456},"images/image07-48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image07.png","integrity":"sha256-SO4WBku5B83bDrLoI8+jtij4hujEPdwPcjPdCcT8Hno=","digest":"48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a","size":36502},"images/image07@2x-6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image07@2x.png","integrity":"sha256-a+Pnas1MOZmfmxfIXgiEgrmLsUt5+cPLMhaYV6Oxc10=","digest":"6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d","size":80453},"images/image08-b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image08.png","integrity":"sha256-sXjHGpsBQH/c3BKfVYH3s3ze3DGgcQHdFPpB/dZndl8=","digest":"b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f","size":16604},"images/image08@2x-2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image08@2x.png","integrity":"sha256-KliMwvrUYCH4Yql16oZzCWf9CxfVgi9SgEI9VIfE3NQ=","digest":"2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4","size":37253},"images/image09-7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image09.png","integrity":"sha256-erQlsybXUINl8oqdG6sM36ngqWBJRIXt0jG5E0h0aqs=","digest":"7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab","size":23801},"images/image09@2x-970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image09@2x.png","integrity":"sha256-lwt40Bbmq64fSeRlbfes7aZhJ4sETUJYhI8coPK1Gt4=","digest":"970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade","size":57479},"images/image10-bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image10.png","integrity":"sha256-v74M+I33vW7/IEo+zrdyaAqXAlO8I/ftCXIUXh2bVoA=","digest":"bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680","size":28270},"images/image10@2x-14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image10@2x.png","integrity":"sha256-FMMBNfJWs9jWegkGVWgxY4BkFEic1ayaj6PSId6hcrw=","digest":"14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc","size":58381},"images/image11-4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image11.png","integrity":"sha256-T7eOUxeqHSxxPLv+cOdgr8xuyp4WQ2uFPT75Am2qDDU=","digest":"4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35","size":35294},"images/image11@2x-62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image11@2x.png","integrity":"sha256-YmQd9ddCJQwU9yDk2bC7unS4f40K8GoggL052cGrzaE=","digest":"62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1","size":82112},"images/image12-ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image12.png","integrity":"sha256-rAGFjOTKWzRdy4Twlw6lDqBHxOZ3nWbGYIkXOze0JmM=","digest":"ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663","size":32045},"images/image12@2x-4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image12@2x.png","integrity":"sha256-Sq1ahEIWCLOWV+YaMuX2TG2epA9JhLn8wPBPwzMxJwY=","digest":"4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706","size":80620},"images/image13-5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image13.png","integrity":"sha256-W3A/sI+2yKaqIuOWR+nhJtZ8oDpIOFGTJSMyHt9fZlc=","digest":"5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657","size":41415},"images/image13@2x-85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image13@2x.png","integrity":"sha256-hSEEYas4QqVfQcxSmsEKef9Ktmdtc7+FvIIwF5B0CFE=","digest":"85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851","size":93815},"images/image14-afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image14.png","integrity":"sha256-r7Up6mG5urpM0QxUsfsQzKW3+pKngm/X67tJ5nG8YGA=","digest":"afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060","size":160525},"images/image14@2x-4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/image14@2x.png","integrity":"sha256-TXCS2ScPLJuUEfbZG5Jdtpd1ZOjdSw3oTShXj9/aZPg=","digest":"4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8","size":536685},"images/logo-5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2.png":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/logo.png","integrity":"sha256-XmWyMZCn5T0gYPqvQ0jOxMIdu2Kv3pgHxzGJYzDR+8I=","digest":"5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2","size":2482},"images/logo-58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010.svg":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/logo.svg","integrity":"sha256-WEkxZWabbzDJ5Lc8jIdIlWRnv8irw7YG2rXHoJbFoBA=","digest":"58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010","size":6023},"images/signal-9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5.svg":{"mtime":"2016-05-17T09:33:32-07:00","logical_path":"images/signal.svg","integrity":"sha256-mEbIuorQeMY7kkkk5emmGl5iCOk/sqjAZr9bds5zBaU=","digest":"9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5","size":1223},"style-aeda5dc64f3b76668e7bdba3c29e9f31be937e57611a7dd015e5cbd79e0dd272.css":{"mtime":"2016-05-18T12:47:52-07:00","logical_path":"style.css","integrity":"sha256-rtpdxk87dmaOe9ujwp6fMb6TfldhGn3QFeXL154N0nI=","digest":"aeda5dc64f3b76668e7bdba3c29e9f31be937e57611a7dd015e5cbd79e0dd272","size":30572},"main-d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423.js":{"mtime":"2016-05-18T12:47:52-07:00","logical_path":"main.js","integrity":"sha256-0Kq9gDlSPPHX9xXBarsVIYSa6cSIGf0xs1S7g9WblCM=","digest":"d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423","size":112931},"analytics-b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7.js":{"mtime":"2016-05-17T17:59:42-07:00","logical_path":"analytics.js","integrity":"sha256-tKZ95WOckFErSrn9lNBFcerZONepjppJ2Fz2rYy2QMc=","digest":"b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7","size":361}},"assets":{"html5-boilerplate/dist/apple-touch-icon.png":"html5-boilerplate/dist/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png","html5-boilerplate/dist/tile-wide.png":"html5-boilerplate/dist/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png","html5-boilerplate/dist/tile.png":"html5-boilerplate/dist/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png","html5-boilerplate/src/apple-touch-icon.png":"html5-boilerplate/src/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png","html5-boilerplate/src/tile-wide.png":"html5-boilerplate/src/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png","html5-boilerplate/src/tile.png":"html5-boilerplate/src/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png","images/02-animation.png":"images/02-animation-66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661.png","images/03-animation.png":"images/03-animation-ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209.png","images/04-animation.png":"images/04-animation-35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba.png","images/05-animation.png":"images/05-animation-9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f.png","images/apple-touch-120x120.png":"images/apple-touch-120x120-8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286.png","images/apple-touch-152x152.png":"images/apple-touch-152x152-9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de.png","images/apple-touch-60x60.png":"images/apple-touch-60x60-9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996.png","images/apple-touch-76x76.png":"images/apple-touch-76x76-7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3.png","images/beacon-graphic.png":"images/beacon-graphic-841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e.png","images/beacon-graphic.svg":"images/beacon-graphic-0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3.svg","images/bg-banner.png":"images/bg-banner-46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69.png","images/bg-banner.svg":"images/bg-banner-a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c.svg","images/image01.png":"images/image01-24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a.png","images/image01@2x.png":"images/image01@2x-c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935.png","images/image02.png":"images/image02-ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8.png","images/image03.png":"images/image03-36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30.png","images/image04.png":"images/image04-4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714.png","images/image05.png":"images/image05-656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad.png","images/image06.png":"images/image06-8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0.png","images/image07.png":"images/image07-48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a.png","images/image07@2x.png":"images/image07@2x-6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d.png","images/image08.png":"images/image08-b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f.png","images/image08@2x.png":"images/image08@2x-2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4.png","images/image09.png":"images/image09-7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab.png","images/image09@2x.png":"images/image09@2x-970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade.png","images/image10.png":"images/image10-bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680.png","images/image10@2x.png":"images/image10@2x-14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc.png","images/image11.png":"images/image11-4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35.png","images/image11@2x.png":"images/image11@2x-62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1.png","images/image12.png":"images/image12-ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663.png","images/image12@2x.png":"images/image12@2x-4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706.png","images/image13.png":"images/image13-5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657.png","images/image13@2x.png":"images/image13@2x-85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851.png","images/image14.png":"images/image14-afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060.png","images/image14@2x.png":"images/image14@2x-4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8.png","images/logo.png":"images/logo-5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2.png","images/logo.svg":"images/logo-58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010.svg","images/signal.svg":"images/signal-9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5.svg","style.css":"style-aeda5dc64f3b76668e7bdba3c29e9f31be937e57611a7dd015e5cbd79e0dd272.css","main.js":"main-d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423.js","analytics.js":"analytics-b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7.js"}}
\ No newline at end of file
diff --git a/assets/analytics-b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7.js b/assets/analytics-b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7.js
new file mode 100644
index 00000000..a53ade54
--- /dev/null
+++ b/assets/analytics-b4a67de5639c90512b4ab9fd94d04571ead938d7a98e9a49d85cf6ad8cb640c7.js
@@ -0,0 +1 @@
+!function(e,a,t,n,c,o,s){e.GoogleAnalyticsObject=c,e[c]=e[c]||function(){(e[c].q=e[c].q||[]).push(arguments)},e[c].l=1*new Date,o=a.createElement(t),s=a.getElementsByTagName(t)[0],o.async=1,o.src=n,s.parentNode.insertBefore(o,s)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-55334818-1","auto"),ga("send","pageview");
\ No newline at end of file
diff --git a/assets/html5-boilerplate/dist/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png b/assets/html5-boilerplate/dist/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png
new file mode 100644
index 00000000..600738f2
Binary files /dev/null and b/assets/html5-boilerplate/dist/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png differ
diff --git a/assets/html5-boilerplate/dist/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png b/assets/html5-boilerplate/dist/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png
new file mode 100644
index 00000000..f820f61a
Binary files /dev/null and b/assets/html5-boilerplate/dist/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png differ
diff --git a/assets/html5-boilerplate/dist/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png b/assets/html5-boilerplate/dist/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png
new file mode 100644
index 00000000..ccd739c7
Binary files /dev/null and b/assets/html5-boilerplate/dist/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png differ
diff --git a/assets/html5-boilerplate/src/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png b/assets/html5-boilerplate/src/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png
new file mode 100644
index 00000000..600738f2
Binary files /dev/null and b/assets/html5-boilerplate/src/apple-touch-icon-cc7d76ac9c4652800d03537c95ea9e2b141311c437d39a0addab1669c723dc21.png differ
diff --git a/assets/html5-boilerplate/src/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png b/assets/html5-boilerplate/src/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png
new file mode 100644
index 00000000..f820f61a
Binary files /dev/null and b/assets/html5-boilerplate/src/tile-80dbde48cec50b0fe1db8739c35cfde8d9b2044f3b36fcdc776d5f3458a31c5f.png differ
diff --git a/assets/html5-boilerplate/src/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png b/assets/html5-boilerplate/src/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png
new file mode 100644
index 00000000..ccd739c7
Binary files /dev/null and b/assets/html5-boilerplate/src/tile-wide-5b5d5c72a79a2c5080db4d5889f287950486c6b89800673fd532d2f59312d9fb.png differ
diff --git a/assets/images/02-animation-66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661.png b/assets/images/02-animation-66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661.png
new file mode 100644
index 00000000..693751be
Binary files /dev/null and b/assets/images/02-animation-66de593e506b36471854cc2d1962c700bd779e57ebe78ed3214558f13f910661.png differ
diff --git a/assets/images/03-animation-ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209.png b/assets/images/03-animation-ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209.png
new file mode 100644
index 00000000..a518bf8b
Binary files /dev/null and b/assets/images/03-animation-ce68f7ce374e378194528f073f63303a8b6fd8a4538a4dfd3ce3afb20868d209.png differ
diff --git a/assets/images/04-animation-35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba.png b/assets/images/04-animation-35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba.png
new file mode 100644
index 00000000..b6139277
Binary files /dev/null and b/assets/images/04-animation-35deaa290ea31b9f3ea48df5c7b2bcb7ddbe14c1220fc567e75de542e9db9cba.png differ
diff --git a/assets/images/05-animation-9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f.png b/assets/images/05-animation-9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f.png
new file mode 100644
index 00000000..092a514f
Binary files /dev/null and b/assets/images/05-animation-9ac2c268f24dd9711a6e83ef5e785d3ea4e4e953046aba37478ad658d8002c3f.png differ
diff --git a/assets/images/apple-touch-120x120-8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286.png b/assets/images/apple-touch-120x120-8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286.png
new file mode 100644
index 00000000..0df423c1
Binary files /dev/null and b/assets/images/apple-touch-120x120-8bf6db590e66835915417defd06e61dffc7e9c1345e90b03210ddd46f49f5286.png differ
diff --git a/assets/images/apple-touch-152x152-9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de.png b/assets/images/apple-touch-152x152-9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de.png
new file mode 100644
index 00000000..d85f931d
Binary files /dev/null and b/assets/images/apple-touch-152x152-9042e990f0ddefbbced1d341b818261471af0a50d7dad39a330b2742bf18e9de.png differ
diff --git a/assets/images/apple-touch-60x60-9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996.png b/assets/images/apple-touch-60x60-9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996.png
new file mode 100644
index 00000000..567aca90
Binary files /dev/null and b/assets/images/apple-touch-60x60-9a0175c37fffcb78a37bf5110dabdf8dc6446c7eac9bc186067f87c8db223996.png differ
diff --git a/assets/images/apple-touch-76x76-7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3.png b/assets/images/apple-touch-76x76-7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3.png
new file mode 100644
index 00000000..a2726d65
Binary files /dev/null and b/assets/images/apple-touch-76x76-7a3108342975a1fc8c46064729b3938f57a380a9f5265e676ffac23073660ca3.png differ
diff --git a/assets/images/beacon-graphic-0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3.svg b/assets/images/beacon-graphic-0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3.svg
new file mode 100644
index 00000000..28ab3703
--- /dev/null
+++ b/assets/images/beacon-graphic-0213cc2231cb5c3401c4ef9537ce7331ce30b846aedfdda84395d572241704e3.svg
@@ -0,0 +1,5603 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
diff --git a/assets/images/beacon-graphic-841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e.png b/assets/images/beacon-graphic-841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e.png
new file mode 100644
index 00000000..dcb237ec
Binary files /dev/null and b/assets/images/beacon-graphic-841baa65afcd1bf1942f7fc179d3321c323a8f1d366255cf9c72bf09e7ce532e.png differ
diff --git a/assets/images/bg-banner-46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69.png b/assets/images/bg-banner-46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69.png
new file mode 100644
index 00000000..53682bb6
Binary files /dev/null and b/assets/images/bg-banner-46f22f4adfea761cad2d45235e2ddc2aaf9e0b6ca1f552c19fb5711be1a68a69.png differ
diff --git a/assets/images/bg-banner-a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c.svg b/assets/images/bg-banner-a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c.svg
new file mode 100644
index 00000000..228d1b3d
--- /dev/null
+++ b/assets/images/bg-banner-a477c9f785c06300dbbdce4dfeed4780ee2c12c902c633cd54f076b9dd21971c.svg
@@ -0,0 +1,142 @@
+
+
+
diff --git a/assets/images/bluetooth_on.png b/assets/images/bluetooth_on.png
new file mode 100644
index 00000000..061ee1cb
Binary files /dev/null and b/assets/images/bluetooth_on.png differ
diff --git a/assets/images/bluetooth_on_large.png b/assets/images/bluetooth_on_large.png
new file mode 100644
index 00000000..0d5e327c
Binary files /dev/null and b/assets/images/bluetooth_on_large.png differ
diff --git a/assets/images/edit_widget.png b/assets/images/edit_widget.png
new file mode 100644
index 00000000..7494196a
Binary files /dev/null and b/assets/images/edit_widget.png differ
diff --git a/assets/images/edit_widget_large.png b/assets/images/edit_widget_large.png
new file mode 100644
index 00000000..1876947c
Binary files /dev/null and b/assets/images/edit_widget_large.png differ
diff --git a/assets/images/first_nearby_notif.png b/assets/images/first_nearby_notif.png
new file mode 100644
index 00000000..dc699ab9
Binary files /dev/null and b/assets/images/first_nearby_notif.png differ
diff --git a/assets/images/first_nearby_notif_large.png b/assets/images/first_nearby_notif_large.png
new file mode 100644
index 00000000..086aee1a
Binary files /dev/null and b/assets/images/first_nearby_notif_large.png differ
diff --git a/assets/images/image01-24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a.png b/assets/images/image01-24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a.png
new file mode 100644
index 00000000..61431289
Binary files /dev/null and b/assets/images/image01-24389daa2fa63841c322c974c538d806fb704408c3bf9f9e49363afec8f4e11a.png differ
diff --git a/assets/images/image01@2x-c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935.png b/assets/images/image01@2x-c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935.png
new file mode 100644
index 00000000..10d9a4b9
Binary files /dev/null and b/assets/images/image01@2x-c89692c50b5333e16ba0218a004dc7e33a6cebed01b10b4431b84b23fc298935.png differ
diff --git a/assets/images/image02-ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8.png b/assets/images/image02-ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8.png
new file mode 100644
index 00000000..02f9c86c
Binary files /dev/null and b/assets/images/image02-ac6ba0aa01183f74e6a846aea71b021ff6a8d52dc5598e5fb5605fae01998fe8.png differ
diff --git a/assets/images/image03-36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30.png b/assets/images/image03-36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30.png
new file mode 100644
index 00000000..fab0b470
Binary files /dev/null and b/assets/images/image03-36f40accaa462b557ea20610f5209acaadf60d9a2e959886a28e413413f04a30.png differ
diff --git a/assets/images/image04-4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714.png b/assets/images/image04-4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714.png
new file mode 100644
index 00000000..b6697334
Binary files /dev/null and b/assets/images/image04-4e9f4672fffa56801a5db6fcca14fa388a37f16a974a64bea242135743e4d714.png differ
diff --git a/assets/images/image05-656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad.png b/assets/images/image05-656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad.png
new file mode 100644
index 00000000..48876494
Binary files /dev/null and b/assets/images/image05-656d9b05da34784495986e63919f1a785572bda9e4c9883da23c50477fd4ddad.png differ
diff --git a/assets/images/image06-8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0.png b/assets/images/image06-8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0.png
new file mode 100644
index 00000000..ae0266e7
Binary files /dev/null and b/assets/images/image06-8057877b079f57fce655ffae09f2b3127772d34d6230240128b4eb2998fbe2f0.png differ
diff --git a/assets/images/image07-48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a.png b/assets/images/image07-48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a.png
new file mode 100644
index 00000000..59c85978
Binary files /dev/null and b/assets/images/image07-48ee16064bb907cddb0eb2e823cfa3b628f886e8c43ddc0f7233dd09c4fc1e7a.png differ
diff --git a/assets/images/image07@2x-6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d.png b/assets/images/image07@2x-6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d.png
new file mode 100644
index 00000000..686b812f
Binary files /dev/null and b/assets/images/image07@2x-6be3e76acd4c39999f9b17c85e088482b98bb14b79f9c3cb32169857a3b1735d.png differ
diff --git a/assets/images/image08-b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f.png b/assets/images/image08-b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f.png
new file mode 100644
index 00000000..2110fb61
Binary files /dev/null and b/assets/images/image08-b178c71a9b01407fdcdc129f5581f7b37cdedc31a07101dd14fa41fdd667765f.png differ
diff --git a/assets/images/image08@2x-2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4.png b/assets/images/image08@2x-2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4.png
new file mode 100644
index 00000000..74cd9065
Binary files /dev/null and b/assets/images/image08@2x-2a588cc2fad46021f862a975ea86730967fd0b17d5822f5280423d5487c4dcd4.png differ
diff --git a/assets/images/image09-7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab.png b/assets/images/image09-7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab.png
new file mode 100644
index 00000000..1b58ddd3
Binary files /dev/null and b/assets/images/image09-7ab425b326d7508365f28a9d1bab0cdfa9e0a960494485edd231b91348746aab.png differ
diff --git a/assets/images/image09@2x-970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade.png b/assets/images/image09@2x-970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade.png
new file mode 100644
index 00000000..96c21d1e
Binary files /dev/null and b/assets/images/image09@2x-970b78d016e6abae1f49e4656df7aceda661278b044d4258848f1ca0f2b51ade.png differ
diff --git a/assets/images/image10-bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680.png b/assets/images/image10-bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680.png
new file mode 100644
index 00000000..a0624634
Binary files /dev/null and b/assets/images/image10-bfbe0cf88df7bd6eff204a3eceb772680a970253bc23f7ed0972145e1d9b5680.png differ
diff --git a/assets/images/image10@2x-14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc.png b/assets/images/image10@2x-14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc.png
new file mode 100644
index 00000000..bad3019b
Binary files /dev/null and b/assets/images/image10@2x-14c30135f256b3d8d67a090655683163806414489cd5ac9a8fa3d221dea172bc.png differ
diff --git a/assets/images/image11-4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35.png b/assets/images/image11-4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35.png
new file mode 100644
index 00000000..d57e0cc2
Binary files /dev/null and b/assets/images/image11-4fb78e5317aa1d2c713cbbfe70e760afcc6eca9e16436b853d3ef9026daa0c35.png differ
diff --git a/assets/images/image11@2x-62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1.png b/assets/images/image11@2x-62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1.png
new file mode 100644
index 00000000..47d1a719
Binary files /dev/null and b/assets/images/image11@2x-62641df5d742250c14f720e4d9b0bbba74b87f8d0af06a2080bd39d9c1abcda1.png differ
diff --git a/assets/images/image12-ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663.png b/assets/images/image12-ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663.png
new file mode 100644
index 00000000..949026c5
Binary files /dev/null and b/assets/images/image12-ac01858ce4ca5b345dcb84f0970ea50ea047c4e6779d66c66089173b37b42663.png differ
diff --git a/assets/images/image12@2x-4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706.png b/assets/images/image12@2x-4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706.png
new file mode 100644
index 00000000..95cbada2
Binary files /dev/null and b/assets/images/image12@2x-4aad5a84421608b39657e61a32e5f64c6d9ea40f4984b9fcc0f04fc333312706.png differ
diff --git a/assets/images/image13-5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657.png b/assets/images/image13-5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657.png
new file mode 100644
index 00000000..59284809
Binary files /dev/null and b/assets/images/image13-5b703fb08fb6c8a6aa22e39647e9e126d67ca03a483851932523321edf5f6657.png differ
diff --git a/assets/images/image13@2x-85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851.png b/assets/images/image13@2x-85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851.png
new file mode 100644
index 00000000..044042f9
Binary files /dev/null and b/assets/images/image13@2x-85210461ab3842a55f41cc529ac10a79ff4ab6676d73bf85bc82301790740851.png differ
diff --git a/assets/images/image14-afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060.png b/assets/images/image14-afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060.png
new file mode 100644
index 00000000..3ad1dee2
Binary files /dev/null and b/assets/images/image14-afb529ea61b9baba4cd10c54b1fb10cca5b7fa92a7826fd7ebbb49e671bc6060.png differ
diff --git a/assets/images/image14@2x-4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8.png b/assets/images/image14@2x-4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8.png
new file mode 100644
index 00000000..7c3583e1
Binary files /dev/null and b/assets/images/image14@2x-4d7092d9270f2c9b9411f6d91b925db6977564e8dd4b0de84d28578fdfda64f8.png differ
diff --git a/assets/images/list_view.png b/assets/images/list_view.png
new file mode 100644
index 00000000..6cd73fd6
Binary files /dev/null and b/assets/images/list_view.png differ
diff --git a/assets/images/list_view_large.png b/assets/images/list_view_large.png
new file mode 100644
index 00000000..2449ed5b
Binary files /dev/null and b/assets/images/list_view_large.png differ
diff --git a/assets/images/logo-58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010.svg b/assets/images/logo-58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010.svg
new file mode 100644
index 00000000..aa0d64a9
--- /dev/null
+++ b/assets/images/logo-58493165669b6f30c9e4b73c8c8748956467bfc8abc3b606dab5c7a096c5a010.svg
@@ -0,0 +1,68 @@
+
+
+
diff --git a/assets/images/logo-5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2.png b/assets/images/logo-5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2.png
new file mode 100644
index 00000000..49126002
Binary files /dev/null and b/assets/images/logo-5e65b23190a7e53d2060faaf4348cec4c21dbb62afde9807c731896330d1fbc2.png differ
diff --git a/assets/images/notif.png b/assets/images/notif.png
new file mode 100644
index 00000000..726a1ffb
Binary files /dev/null and b/assets/images/notif.png differ
diff --git a/assets/images/notif_large.png b/assets/images/notif_large.png
new file mode 100644
index 00000000..1c23b075
Binary files /dev/null and b/assets/images/notif_large.png differ
diff --git a/assets/images/opt_in.png b/assets/images/opt_in.png
new file mode 100644
index 00000000..8f4addba
Binary files /dev/null and b/assets/images/opt_in.png differ
diff --git a/assets/images/opt_in_large.png b/assets/images/opt_in_large.png
new file mode 100644
index 00000000..79247971
Binary files /dev/null and b/assets/images/opt_in_large.png differ
diff --git a/assets/images/settings.png b/assets/images/settings.png
new file mode 100644
index 00000000..3f4e88a7
Binary files /dev/null and b/assets/images/settings.png differ
diff --git a/assets/images/settings_large.png b/assets/images/settings_large.png
new file mode 100644
index 00000000..a1614db6
Binary files /dev/null and b/assets/images/settings_large.png differ
diff --git a/assets/images/signal-9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5.svg b/assets/images/signal-9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5.svg
new file mode 100644
index 00000000..b379fccf
--- /dev/null
+++ b/assets/images/signal-9846c8ba8ad078c63b924924e5e9a61a5e6208e93fb2a8c066bf5b76ce7305a5.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/assets/images/widget_added.png b/assets/images/widget_added.png
new file mode 100644
index 00000000..dc08aa40
Binary files /dev/null and b/assets/images/widget_added.png differ
diff --git a/assets/images/widget_added_large.png b/assets/images/widget_added_large.png
new file mode 100644
index 00000000..f1c173cd
Binary files /dev/null and b/assets/images/widget_added_large.png differ
diff --git a/assets/images/widget_not_added.png b/assets/images/widget_not_added.png
new file mode 100644
index 00000000..0e3b71de
Binary files /dev/null and b/assets/images/widget_not_added.png differ
diff --git a/assets/images/widget_not_added_large.png b/assets/images/widget_not_added_large.png
new file mode 100644
index 00000000..42fe1c20
Binary files /dev/null and b/assets/images/widget_not_added_large.png differ
diff --git a/assets/images/widget_results.png b/assets/images/widget_results.png
new file mode 100644
index 00000000..b4ca7c5b
Binary files /dev/null and b/assets/images/widget_results.png differ
diff --git a/assets/images/widget_results_large.png b/assets/images/widget_results_large.png
new file mode 100644
index 00000000..119f3810
Binary files /dev/null and b/assets/images/widget_results_large.png differ
diff --git a/assets/main-d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423.js b/assets/main-d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423.js
new file mode 100644
index 00000000..a87aa929
--- /dev/null
+++ b/assets/main-d0aabd8039523cf1d7f715c16abb1521849ae9c48819fd31b354bb83d59b9423.js
@@ -0,0 +1,16 @@
+window.Modernizr=function(e,t,n){function r(e){x.cssText=e}function i(e,t){return r(C.join(e+";")+(t||""))}function o(e,t){return typeof e===t}function a(e,t){return!!~(""+e).indexOf(t)}function s(e,t){for(var r in e){var i=e[r];if(!a(i,"-")&&x[i]!==n)return"pfx"==t?i:!0}return!1}function u(e,t,r){for(var i in e){var a=t[e[i]];if(a!==n)return r===!1?e[i]:o(a,"function")?a.bind(r||t):a}return!1}function l(e,t,n){var r=e.charAt(0).toUpperCase()+e.slice(1),i=(e+" "+k.join(r+" ")+r).split(" ");return o(t,"string")||o(t,"undefined")?s(i,t):(i=(e+" "+E.join(r+" ")+r).split(" "),u(i,t,n))}function c(){h.input=function(n){for(var r=0,i=n.length;i>r;r++)D[n[r]]=n[r]in w;return D.list&&(D.list=!!t.createElement("datalist")&&!!e.HTMLDataListElement),D}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),h.inputtypes=function(e){for(var r,i,o,a=0,s=e.length;s>a;a++)w.setAttribute("type",i=e[a]),r="text"!==w.type,r&&(w.value=b,w.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(i)&&w.style.WebkitAppearance!==n?(m.appendChild(w),o=t.defaultView,r=o.getComputedStyle&&"textfield"!==o.getComputedStyle(w,null).WebkitAppearance&&0!==w.offsetHeight,m.removeChild(w)):/^(search|tel)$/.test(i)||(r=/^(url|email)$/.test(i)?w.checkValidity&&w.checkValidity()===!1:w.value!=b)),j[e[a]]=!!r;return j}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var f,p,d="2.8.3",h={},g=!0,m=t.documentElement,v="modernizr",y=t.createElement(v),x=y.style,w=t.createElement("input"),b=":)",T={}.toString,C=" -webkit- -moz- -o- -ms- ".split(" "),S="Webkit Moz O ms",k=S.split(" "),E=S.toLowerCase().split(" "),A={svg:"http://www.w3.org/2000/svg"},N={},j={},D={},q=[],L=q.slice,H=function(e,n,r,i){var o,a,s,u,l=t.createElement("div"),c=t.body,f=c||t.createElement("body");if(parseInt(r,10))for(;r--;)s=t.createElement("div"),s.id=i?i[r]:v+(r+1),l.appendChild(s);return o=["",'"].join(""),l.id=v,(c?l:f).innerHTML+=o,f.appendChild(l),c||(f.style.background="",f.style.overflow="hidden",u=m.style.overflow,m.style.overflow="hidden",m.appendChild(f)),a=n(l,e),c?l.parentNode.removeChild(l):(f.parentNode.removeChild(f),m.style.overflow=u),!!a},P=function(t){var n=e.matchMedia||e.msMatchMedia;if(n)return n(t)&&n(t).matches||!1;var r;return H("@media "+t+" { #"+v+" { position: absolute; } }",function(t){r="absolute"==(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle).position}),r},O=function(){function e(e,i){i=i||t.createElement(r[e]||"div"),e="on"+e;var a=e in i;return a||(i.setAttribute||(i=t.createElement("div")),i.setAttribute&&i.removeAttribute&&(i.setAttribute(e,""),a=o(i[e],"function"),o(i[e],"undefined")||(i[e]=n),i.removeAttribute(e))),i=null,a}var r={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return e}(),F={}.hasOwnProperty;p=o(F,"undefined")||o(F.call,"undefined")?function(e,t){return t in e&&o(e.constructor.prototype[t],"undefined")}:function(e,t){return F.call(e,t)},Function.prototype.bind||(Function.prototype.bind=function(e){var t=this;if("function"!=typeof t)throw new TypeError;var n=L.call(arguments,1),r=function(){if(this instanceof r){var i=function(){};i.prototype=t.prototype;var o=new i,a=t.apply(o,n.concat(L.call(arguments)));return Object(a)===a?a:o}return t.apply(e,n.concat(L.call(arguments)))};return r}),N.flexbox=function(){return l("flexWrap")},N.canvas=function(){var e=t.createElement("canvas");return!!e.getContext&&!!e.getContext("2d")},N.canvastext=function(){return!!h.canvas&&!!o(t.createElement("canvas").getContext("2d").fillText,"function")},N.webgl=function(){return!!e.WebGLRenderingContext},N.touch=function(){var n;return"ontouchstart"in e||e.DocumentTouch&&t instanceof DocumentTouch?n=!0:H(["@media (",C.join("touch-enabled),("),v,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(e){n=9===e.offsetTop}),n},N.geolocation=function(){return"geolocation"in navigator},N.postmessage=function(){return!!e.postMessage},N.websqldatabase=function(){return!!e.openDatabase},N.indexedDB=function(){return!!l("indexedDB",e)},N.hashchange=function(){return O("hashchange",e)&&(t.documentMode===n||t.documentMode>7)},N.history=function(){return!!e.history&&!!history.pushState},N.draganddrop=function(){var e=t.createElement("div");return"draggable"in e||"ondragstart"in e&&"ondrop"in e},N.websockets=function(){return"WebSocket"in e||"MozWebSocket"in e},N.rgba=function(){return r("background-color:rgba(150,255,150,.5)"),a(x.backgroundColor,"rgba")},N.hsla=function(){return r("background-color:hsla(120,40%,100%,.5)"),a(x.backgroundColor,"rgba")||a(x.backgroundColor,"hsla")},N.multiplebgs=function(){return r("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(x.background)},N.backgroundsize=function(){return l("backgroundSize")},N.borderimage=function(){return l("borderImage")},N.borderradius=function(){return l("borderRadius")},N.boxshadow=function(){return l("boxShadow")},N.textshadow=function(){return""===t.createElement("div").style.textShadow},N.opacity=function(){return i("opacity:.55"),/^0.55$/.test(x.opacity)},N.cssanimations=function(){return l("animationName")},N.csscolumns=function(){return l("columnCount")},N.cssgradients=function(){var e="background-image:",t="gradient(linear,left top,right bottom,from(#9f9),to(white));",n="linear-gradient(left top,#9f9, white);";return r((e+"-webkit- ".split(" ").join(t+e)+C.join(n+e)).slice(0,-e.length)),a(x.backgroundImage,"gradient")},N.cssreflections=function(){return l("boxReflect")},N.csstransforms=function(){return!!l("transform")},N.csstransforms3d=function(){var e=!!l("perspective");return e&&"webkitPerspective"in m.style&&H("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(t){e=9===t.offsetLeft&&3===t.offsetHeight}),e},N.csstransitions=function(){return l("transition")},N.fontface=function(){var e;return H('@font-face {font-family:"font";src:url("https://")}',function(n,r){var i=t.getElementById("smodernizr"),o=i.sheet||i.styleSheet,a=o?o.cssRules&&o.cssRules[0]?o.cssRules[0].cssText:o.cssText||"":"";e=/src/i.test(a)&&0===a.indexOf(r.split(" ")[0])}),e},N.generatedcontent=function(){var e;return H(["#",v,"{font:0/0 a}#",v,':after{content:"',b,'";visibility:hidden;font:3px/1 a}'].join(""),function(t){e=t.offsetHeight>=3}),e},N.video=function(){var e=t.createElement("video"),n=!1;try{(n=!!e.canPlayType)&&(n=new Boolean(n),n.ogg=e.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),n.h264=e.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),n.webm=e.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""))}catch(r){}return n},N.audio=function(){var e=t.createElement("audio"),n=!1;try{(n=!!e.canPlayType)&&(n=new Boolean(n),n.ogg=e.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),n.mp3=e.canPlayType("audio/mpeg;").replace(/^no$/,""),n.wav=e.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),n.m4a=(e.canPlayType("audio/x-m4a;")||e.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(r){}return n},N.localstorage=function(){try{return localStorage.setItem(v,v),localStorage.removeItem(v),!0}catch(e){return!1}},N.sessionstorage=function(){try{return sessionStorage.setItem(v,v),sessionStorage.removeItem(v),!0}catch(e){return!1}},N.webworkers=function(){return!!e.Worker},N.applicationcache=function(){return!!e.applicationCache},N.svg=function(){return!!t.createElementNS&&!!t.createElementNS(A.svg,"svg").createSVGRect},N.inlinesvg=function(){var e=t.createElement("div");return e.innerHTML="",(e.firstChild&&e.firstChild.namespaceURI)==A.svg},N.smil=function(){return!!t.createElementNS&&/SVGAnimate/.test(T.call(t.createElementNS(A.svg,"animate")))},N.svgclippaths=function(){return!!t.createElementNS&&/SVGClipPath/.test(T.call(t.createElementNS(A.svg,"clipPath")))};for(var R in N)p(N,R)&&(f=R.toLowerCase(),h[f]=N[R](),q.push((h[f]?"":"no-")+f));return h.input||c(),h.addTest=function(e,t){if("object"==typeof e)for(var r in e)p(e,r)&&h.addTest(r,e[r]);else{if(e=e.toLowerCase(),h[e]!==n)return h;t="function"==typeof t?t():t,"undefined"!=typeof g&&g&&(m.className+=" "+(t?"":"no-")+e),h[e]=t}return h},r(""),y=w=null,function(e,t){function n(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x",r.insertBefore(n.lastChild,r.firstChild)}function r(){var e=y.elements;return"string"==typeof e?e.split(" "):e}function i(e){var t=v[e[g]];return t||(t={},m++,e[g]=m,v[m]=t),t}function o(e,n,r){if(n||(n=t),c)return n.createElement(e);r||(r=i(n));var o;return o=r.cache[e]?r.cache[e].cloneNode():h.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!o.canHaveChildren||d.test(e)||o.tagUrn?o:r.frag.appendChild(o)}function a(e,n){if(e||(e=t),c)return e.createDocumentFragment();n=n||i(e);for(var o=n.frag.cloneNode(),a=0,s=r(),u=s.length;u>a;a++)o.createElement(s[a]);return o}function s(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return y.shivMethods?o(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+r().join().replace(/[\w\-]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(y,t.frag)}function u(e){e||(e=t);var r=i(e);return y.shivCSS&&!l&&!r.hasCSS&&(r.hasCSS=!!n(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),c||s(e,r),e}var l,c,f="3.7.0",p=e.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,h=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g="_html5shiv",m=0,v={};!function(){try{var e=t.createElement("a");e.innerHTML="",l="hidden"in e,c=1==e.childNodes.length||function(){t.createElement("a");var e=t.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(n){l=!0,c=!0}}();var y={elements:p.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:f,shivCSS:p.shivCSS!==!1,supportsUnknownElements:c,shivMethods:p.shivMethods!==!1,type:"default",shivDocument:u,createElement:o,createDocumentFragment:a};e.html5=y,u(t)}(this,t),h._version=d,h._prefixes=C,h._domPrefixes=E,h._cssomPrefixes=k,h.mq=P,h.hasEvent=O,h.testProp=function(e){return s([e])},h.testAllProps=l,h.testStyles=H,h.prefixed=function(e,t,n){return t?l(e,t,n):l(e,"pfx")},m.className=m.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(g?" js "+q.join(" "):""),h}(this,this.document),function(e,t,n){function r(e){return"[object Function]"==m.call(e)}function i(e){return"string"==typeof e}function o(){}function a(e){return!e||"loaded"==e||"complete"==e||"uninitialized"==e}function s(){var e=v.shift();y=1,e?e.t?h(function(){("c"==e.t?p.injectCss:p.injectJs)(e.s,0,e.a,e.x,e.e,1)},0):(e(),s()):y=0}function u(e,n,r,i,o,u,l){function c(t){if(!d&&a(f.readyState)&&(x.r=d=1,!y&&s(),f.onload=f.onreadystatechange=null,t)){"img"!=e&&h(function(){b.removeChild(f)},50);for(var r in E[n])E[n].hasOwnProperty(r)&&E[n][r].onload()}}var l=l||p.errorTimeout,f=t.createElement(e),d=0,m=0,x={t:r,s:n,e:o,a:u,x:l};1===E[n]&&(m=1,E[n]=[]),"object"==e?f.data=n:(f.src=n,f.type=e),f.width=f.height="0",f.onerror=f.onload=f.onreadystatechange=function(){c.call(this,m)},v.splice(i,0,x),"img"!=e&&(m||2===E[n]?(b.insertBefore(f,w?null:g),h(c,l)):E[n].push(f))}function l(e,t,n,r,o){return y=0,t=t||"j",i(e)?u("c"==t?C:T,e,t,this.i++,n,r,o):(v.splice(this.i++,0,e),1==v.length&&s()),this}function c(){var e=p;return e.loader={load:l,i:0},e}var f,p,d=t.documentElement,h=e.setTimeout,g=t.getElementsByTagName("script")[0],m={}.toString,v=[],y=0,x="MozAppearance"in d.style,w=x&&!!t.createRange().compareNode,b=w?d:g.parentNode,d=e.opera&&"[object Opera]"==m.call(e.opera),d=!!t.attachEvent&&!d,T=x?"object":d?"script":"img",C=d?"script":T,S=Array.isArray||function(e){return"[object Array]"==m.call(e)},k=[],E={},A={timeout:function(e,t){return t.length&&(e.timeout=t[0]),e}};p=function(e){function t(e){var t,n,r,e=e.split("!"),i=k.length,o=e.pop(),a=e.length,o={url:o,origUrl:o,prefixes:e};for(n=0;a>n;n++)r=e[n].split("="),(t=A[r.shift()])&&(o=t(o,r));for(n=0;i>n;n++)o=k[n](o);return o}function a(e,i,o,a,s){var u=t(e),l=u.autoCallback;u.url.split(".").pop().split("?").shift(),u.bypass||(i&&(i=r(i)?i:i[e]||i[a]||i[e.split("/").pop().split("?")[0]]),u.instead?u.instead(e,i,o,a,s):(E[u.url]?u.noexec=!0:E[u.url]=1,o.load(u.url,u.forceCSS||!u.forceJS&&"css"==u.url.split(".").pop().split("?").shift()?"c":n,u.noexec,u.attrs,u.timeout),(r(i)||r(l))&&o.load(function(){c(),i&&i(u.origUrl,s,a),l&&l(u.origUrl,s,a),E[u.url]=2})))}function s(e,t){function n(e,n){if(e){if(i(e))n||(f=function(){var e=[].slice.call(arguments);p.apply(this,e),d()}),a(e,f,t,0,l);else if(Object(e)===e)for(u in s=function(){var t,n=0;for(t in e)e.hasOwnProperty(t)&&n++;return n}(),e)e.hasOwnProperty(u)&&(!n&&!--s&&(r(f)?f=function(){var e=[].slice.call(arguments);p.apply(this,e),d()}:f[u]=function(e){return function(){var t=[].slice.call(arguments);e&&e.apply(this,t),d()}}(p[u])),a(e[u],f,t,u,l))}else!n&&d()}var s,u,l=!!e.test,c=e.load||e.both,f=e.callback||o,p=f,d=e.complete||o;n(l?e.yep:e.nope,!!c),c&&n(c)}var u,l,f=this.yepnope.loader;if(i(e))a(e,0,f,0);else if(S(e))for(u=0;u0&&t-1 in e}function r(e,t,n){if(oe.isFunction(t))return oe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return oe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(ge.test(t))return oe.filter(t,e,n);t=oe.filter(t,e)}return oe.grep(e,function(e){return Z.call(t,e)>-1!==n})}function i(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function o(e){var t={};return oe.each(e.match(be)||[],function(e,n){t[n]=!0}),t}function a(){G.removeEventListener("DOMContentLoaded",a),e.removeEventListener("load",a),oe.ready()}function s(){this.expando=oe.expando+s.uid++}function u(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Ne,"-$&").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Ae.test(n)?oe.parseJSON(n):n}catch(i){}Ee.set(e,t,n)}else n=void 0;return n}function l(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return oe.css(e,t,"")},u=s(),l=n&&n[3]||(oe.cssNumber[t]?"":"px"),c=(oe.cssNumber[t]||"px"!==l&&+u)&&De.exec(oe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||".5",c/=o,oe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function c(e,t){var n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&oe.nodeName(e,t)?oe.merge([e],n):n}function f(e,t){for(var n=0,r=e.length;r>n;n++)ke.set(e[n],"globalEval",!t||ke.get(t[n],"globalEval"))}function p(e,t,n,r,i){for(var o,a,s,u,l,p,d=t.createDocumentFragment(),h=[],g=0,m=e.length;m>g;g++)if(o=e[g],o||0===o)if("object"===oe.type(o))oe.merge(h,o.nodeType?[o]:o);else if(Re.test(o)){for(a=a||d.appendChild(t.createElement("div")),s=(Pe.exec(o)||["",""])[1].toLowerCase(),u=Fe[s]||Fe._default,a.innerHTML=u[1]+oe.htmlPrefilter(o)+u[2],p=u[0];p--;)a=a.lastChild;oe.merge(h,a.childNodes),a=d.firstChild,a.textContent=""}else h.push(t.createTextNode(o));for(d.textContent="",g=0;o=h[g++];)if(r&&oe.inArray(o,r)>-1)i&&i.push(o);else if(l=oe.contains(o.ownerDocument,o),a=c(d.appendChild(o),"script"),l&&f(a),n)for(p=0;o=a[p++];)Oe.test(o.type||"")&&n.push(o);return d}function d(){return!0}function h(){return!1}function g(){try{return G.activeElement}catch(e){}}function m(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)m(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=h;else if(!i)return e;return 1===o&&(a=i,i=function(e){return oe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=oe.guid++)),e.each(function(){oe.event.add(this,t,i,r,n)})}function v(e,t){return oe.nodeName(e,"table")&&oe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function y(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function x(e){var t=_e.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(ke.hasData(e)&&(o=ke.access(e),a=ke.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)oe.event.add(t,i,l[i][n])}Ee.hasData(e)&&(s=Ee.access(e),u=oe.extend({},s),Ee.set(t,u))}}function b(e,t){var n=t.nodeName.toLowerCase();"input"===n&&He.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function T(e,t,n,r){t=J.apply([],t);var i,o,a,s,u,l,f=0,d=e.length,h=d-1,g=t[0],m=oe.isFunction(g);if(m||d>1&&"string"==typeof g&&!re.checkClone&&Be.test(g))return e.each(function(i){var o=e.eq(i);m&&(t[0]=g.call(this,i,o.html())),T(o,t,n,r)});if(d&&(i=p(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(a=oe.map(c(i,"script"),y),s=a.length;d>f;f++)u=i,f!==h&&(u=oe.clone(u,!0,!0),s&&oe.merge(a,c(u,"script"))),n.call(e[f],u,f);if(s)for(l=a[a.length-1].ownerDocument,oe.map(a,x),f=0;s>f;f++)u=a[f],Oe.test(u.type||"")&&!ke.access(u,"globalEval")&&oe.contains(l,u)&&(u.src?oe._evalUrl&&oe._evalUrl(u.src):oe.globalEval(u.textContent.replace(Xe,"")))}return e}function C(e,t,n){for(var r,i=t?oe.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||oe.cleanData(c(r)),r.parentNode&&(n&&oe.contains(r.ownerDocument,r)&&f(c(r,"script")),r.parentNode.removeChild(r));return e}function S(e,t){var n=oe(t.createElement(e)).appendTo(t.body),r=oe.css(n[0],"display");return n.detach(),r}function k(e){var t=G,n=Ve[e];return n||(n=S(e,t),"none"!==n&&n||(Ue=(Ue||oe("")).appendTo(t.documentElement),t=Ue[0].contentDocument,t.write(),t.close(),n=S(e,t),Ue.detach()),Ve[e]=n),n}function E(e,t,n){var r,i,o,a,s=e.style;return n=n||Ye(e),a=n?n.getPropertyValue(t)||n[t]:void 0,""!==a&&void 0!==a||oe.contains(e.ownerDocument,e)||(a=oe.style(e,t)),n&&!re.pixelMarginRight()&&Ge.test(a)&&Qe.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o),void 0!==a?a+"":a}function A(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function N(e){if(e in rt)return e;for(var t=e[0].toUpperCase()+e.slice(1),n=nt.length;n--;)if(e=nt[n]+t,e in rt)return e}function j(e,t,n){var r=De.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function D(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=oe.css(e,n+qe[o],!0,i)),r?("content"===n&&(a-=oe.css(e,"padding"+qe[o],!0,i)),"margin"!==n&&(a-=oe.css(e,"border"+qe[o]+"Width",!0,i))):(a+=oe.css(e,"padding"+qe[o],!0,i),"padding"!==n&&(a+=oe.css(e,"border"+qe[o]+"Width",!0,i)));return a}function q(t,n,r){var i=!0,o="width"===n?t.offsetWidth:t.offsetHeight,a=Ye(t),s="border-box"===oe.css(t,"boxSizing",!1,a);if(G.msFullscreenElement&&e.top!==e&&t.getClientRects().length&&(o=Math.round(100*t.getBoundingClientRect()[n])),0>=o||null==o){if(o=E(t,n,a),(0>o||null==o)&&(o=t.style[n]),Ge.test(o))return o;i=s&&(re.boxSizingReliable()||o===t.style[n]),o=parseFloat(o)||0}return o+D(t,n,r||(s?"border":"content"),i,a)+"px"}function L(e,t){for(var n,r,i,o=[],a=0,s=e.length;s>a;a++)r=e[a],r.style&&(o[a]=ke.get(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&Le(r)&&(o[a]=ke.access(r,"olddisplay",k(r.nodeName)))):(i=Le(r),"none"===n&&i||ke.set(r,"olddisplay",i?n:oe.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}function H(e,t,n,r,i){return new H.prototype.init(e,t,n,r,i)}function P(){return e.setTimeout(function(){it=void 0}),it=oe.now()}function O(e,t){var n,r=0,i={height:e};for(t=t?1:0;4>r;r+=2-t)n=qe[r],i["margin"+n]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function F(e,t,n){for(var r,i=(M.tweeners[t]||[]).concat(M.tweeners["*"]),o=0,a=i.length;a>o;o++)if(r=i[o].call(n,t,e))return r}function R(e,t,n){var r,i,o,a,s,u,l,c,f=this,p={},d=e.style,h=e.nodeType&&Le(e),g=ke.get(e,"fxshow");n.queue||(s=oe._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,u=s.empty.fire,s.empty.fire=function(){s.unqueued||u()}),s.unqueued++,f.always(function(){f.always(function(){s.unqueued--,oe.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],l=oe.css(e,"display"),c="none"===l?ke.get(e,"olddisplay")||k(e.nodeName):l,"inline"===c&&"none"===oe.css(e,"float")&&(d.display="inline-block")),n.overflow&&(d.overflow="hidden",f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],at.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(h?"hide":"show")){if("show"!==i||!g||void 0===g[r])continue;h=!0}p[r]=g&&g[r]||oe.style(e,r)}else l=void 0;if(oe.isEmptyObject(p))"inline"===("none"===l?k(e.nodeName):l)&&(d.display=l);else{g?"hidden"in g&&(h=g.hidden):g=ke.access(e,"fxshow",{}),o&&(g.hidden=!h),h?oe(e).show():f.done(function(){oe(e).hide()}),f.done(function(){var t;ke.remove(e,"fxshow");for(t in p)oe.style(e,t,p[t])});for(r in p)a=F(h?g[r]:0,r,f),r in g||(g[r]=a.start,h&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function W(e,t){var n,r,i,o,a;for(n in e)if(r=oe.camelCase(n),i=t[r],o=e[n],oe.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=oe.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function M(e,t,n){var r,i,o=0,a=M.prefilters.length,s=oe.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=it||P(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:oe.extend({},t),opts:oe.extend(!0,{specialEasing:{},easing:oe.easing._default},n),originalProperties:t,originalOptions:n,startTime:it||P(),duration:n.duration,tweens:[],createTween:function(t,n){var r=oe.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(W(c,l.opts.specialEasing);a>o;o++)if(r=M.prefilters[o].call(l,e,c,l.opts))return oe.isFunction(r.stop)&&(oe._queueHooks(l.elem,l.opts.queue).stop=oe.proxy(r.stop,r)),r;return oe.map(c,F,l),oe.isFunction(l.opts.start)&&l.opts.start.call(e,l),oe.fx.timer(oe.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function $(e){return e.getAttribute&&e.getAttribute("class")||""}function z(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(be)||[];if(oe.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function I(e,t,n,r){function i(s){var u;return o[s]=!0,oe.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||a||o[l]?a?!(u=l):void 0:(t.dataTypes.unshift(l),i(l),!1)}),u}var o={},a=e===Et;return i(t.dataTypes[0])||!o["*"]&&i("*")}function B(e,t){var n,r,i=oe.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&oe.extend(!0,e,r),e}function _(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}return o?(o!==u[0]&&u.unshift(o),n[o]):void 0}function X(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(a=l[u+" "+o]||l["* "+o],!a)for(i in l)if(s=i.split(" "),s[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){a===!0?a=l[i]:l[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(f){return{state:"parsererror",error:a?f:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}function U(e,t,n,r){var i;if(oe.isArray(t))oe.each(t,function(t,i){n||Dt.test(e)?r(e,i):U(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==oe.type(t))r(e,t);else for(i in t)U(e+"["+i+"]",t[i],n,r)}function V(e){return oe.isWindow(e)?e:9===e.nodeType&&e.defaultView}var Q=[],G=e.document,Y=Q.slice,J=Q.concat,K=Q.push,Z=Q.indexOf,ee={},te=ee.toString,ne=ee.hasOwnProperty,re={},ie="2.2.2",oe=function(e,t){return new oe.fn.init(e,t)},ae=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,se=/^-ms-/,ue=/-([\da-z])/gi,le=function(e,t){return t.toUpperCase()};oe.fn=oe.prototype={jquery:ie,constructor:oe,selector:"",length:0,toArray:function(){return Y.call(this)},get:function(e){return null!=e?0>e?this[e+this.length]:this[e]:Y.call(this)},pushStack:function(e){var t=oe.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e){return oe.each(this,e)},map:function(e){return this.pushStack(oe.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(Y.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:K,sort:Q.sort,splice:Q.splice},oe.extend=oe.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||oe.isFunction(a)||(a={}),s===u&&(a=this,s--);u>s;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],r=e[t],a!==r&&(l&&r&&(oe.isPlainObject(r)||(i=oe.isArray(r)))?(i?(i=!1,o=n&&oe.isArray(n)?n:[]):o=n&&oe.isPlainObject(n)?n:{},a[t]=oe.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},oe.extend({expando:"jQuery"+(ie+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isFunction:function(e){return"function"===oe.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){var t=e&&e.toString();return!oe.isArray(e)&&t-parseFloat(t)+1>=0},isPlainObject:function(e){var t;if("object"!==oe.type(e)||e.nodeType||oe.isWindow(e))return!1;if(e.constructor&&!ne.call(e,"constructor")&&!ne.call(e.constructor.prototype||{},"isPrototypeOf"))return!1;for(t in e);return void 0===t||ne.call(e,t)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ee[te.call(e)]||"object":typeof e},globalEval:function(e){var t,n=eval;e=oe.trim(e),e&&(1===e.indexOf("use strict")?(t=G.createElement("script"),t.text=e,G.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(se,"ms-").replace(ue,le)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;r>i&&t.call(e[i],i,e[i])!==!1;i++);else for(i in e)if(t.call(e[i],i,e[i])===!1)break;return e},trim:function(e){return null==e?"":(e+"").replace(ae,"")},makeArray:function(e,t){var r=t||[];return null!=e&&(n(Object(e))?oe.merge(r,"string"==typeof e?[e]:e):K.call(r,e)),r},inArray:function(e,t,n){return null==t?-1:Z.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;n>r;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;a>o;o++)r=!t(e[o],o),r!==s&&i.push(e[o]);return i},map:function(e,t,r){var i,o,a=0,s=[];if(n(e))for(i=e.length;i>a;a++)o=t(e[a],a,r),null!=o&&s.push(o);else for(a in e)o=t(e[a],a,r),null!=o&&s.push(o);return J.apply([],s)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),oe.isFunction(e)?(r=Y.call(arguments,2),i=function(){return e.apply(t||this,r.concat(Y.call(arguments)))},i.guid=e.guid=e.guid||oe.guid++,i):void 0},now:Date.now,support:re}),"function"==typeof Symbol&&(oe.fn[Symbol.iterator]=Q[Symbol.iterator]),oe.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){ee["[object "+t+"]"]=t.toLowerCase()});var ce=function(e){function t(e,t,n,r){var i,o,a,s,u,l,f,d,h=t&&t.ownerDocument,g=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==g&&9!==g&&11!==g)return n;if(!r&&((t?t.ownerDocument||t:$)!==L&&q(t),t=t||L,P)){if(11!==g&&(l=ve.exec(e)))if(i=l[1]){if(9===g){if(!(a=t.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(h&&(a=h.getElementById(i))&&W(t,a)&&a.id===i)return n.push(a),n}else{if(l[2])return K.apply(n,t.getElementsByTagName(e)),n;if((i=l[3])&&b.getElementsByClassName&&t.getElementsByClassName)return K.apply(n,t.getElementsByClassName(i)),n}if(b.qsa&&!X[e+" "]&&(!O||!O.test(e))){if(1!==g)h=t,d=e;else if("object"!==t.nodeName.toLowerCase()){for((s=t.getAttribute("id"))?s=s.replace(xe,"\\$&"):t.setAttribute("id",s=M),f=k(e),o=f.length,u=pe.test(s)?"#"+s:"[id='"+s+"']";o--;)f[o]=u+" "+p(f[o]);d=f.join(","),h=ye.test(e)&&c(t.parentNode)||t}if(d)try{return K.apply(n,h.querySelectorAll(d)),n}catch(m){}finally{s===M&&t.removeAttribute("id")}}}return A(e.replace(se,"$1"),t,n,r)}function n(){function e(n,r){return t.push(n+" ")>T.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[M]=!0,e}function i(e){var t=L.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function f(){}function p(e){for(var t=0,n=e.length,r="";n>t;t++)r+=e[t].value;return r}function d(e,t,n){var r=t.dir,i=n&&"parentNode"===r,o=I++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,u,l,c=[z,o];if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i){if(l=t[M]||(t[M]={}),u=l[t.uniqueID]||(l[t.uniqueID]={}),(s=u[r])&&s[0]===z&&s[1]===o)return c[2]=s[2];if(u[r]=c,c[2]=e(t,n,a))return!0}}}function h(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;o>i;i++)t(e,n[i],r);return r}function m(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;u>s;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function v(e,t,n,i,o,a){return i&&!i[M]&&(i=v(i)),o&&!o[M]&&(o=v(o,a)),r(function(r,a,s,u){var l,c,f,p=[],d=[],h=a.length,v=r||g(t||"*",s.nodeType?[s]:s,[]),y=!e||!r&&t?v:m(v,p,e,s,u),x=n?o||(r?e:h||i)?[]:a:y;if(n&&n(y,x,s,u),i)for(l=m(x,d),i(l,[],s,u),c=l.length;c--;)(f=l[c])&&(x[d[c]]=!(y[d[c]]=f));if(r){if(o||e){if(o){for(l=[],c=x.length;c--;)(f=x[c])&&l.push(y[c]=f);o(null,x=[],l,u)}for(c=x.length;c--;)(f=x[c])&&(l=o?ee(r,f):p[c])>-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):K.apply(a,x)})}function y(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[" "],s=o?1:0,u=d(function(e){return e===t},a,!0),l=d(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==N)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];i>s;s++)if(n=T.relative[e[s].type])c=[d(h(c),n)];else{if(n=T.filter[e[s].type].apply(null,e[s].matches),n[M]){for(r=++s;i>r&&!T.relative[e[r].type];r++);return v(s>1&&h(c),s>1&&p(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(se,"$1"),n,r>s&&y(e.slice(s,r)),i>r&&y(e=e.slice(r)),i>r&&p(e))}c.push(n)}return h(c)}function x(e,n){var i=n.length>0,o=e.length>0,a=function(r,a,s,u,l){var c,f,p,d=0,h="0",g=r&&[],v=[],y=N,x=r||o&&T.find.TAG("*",l),w=z+=null==y?1:Math.random()||.1,b=x.length;for(l&&(N=a===L||a||l);h!==b&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===L||(q(c),s=!P);p=e[f++];)if(p(c,a||L,s)){u.push(c);break}l&&(z=w)}i&&((c=!p&&c)&&d--,r&&g.push(c))}if(d+=h,i&&h!==d){for(f=0;p=n[f++];)p(g,v,a,s);if(r){if(d>0)for(;h--;)g[h]||v[h]||(v[h]=Y.call(u));v=m(v)}K.apply(u,v),l&&!r&&v.length>0&&d+n.length>1&&t.uniqueSort(u)}return l&&(z=w,N=y),g};return i?r(a):a}var w,b,T,C,S,k,E,A,N,j,D,q,L,H,P,O,F,R,W,M="sizzle"+1*new Date,$=e.document,z=0,I=0,B=n(),_=n(),X=n(),U=function(e,t){return e===t&&(D=!0),0},V=1<<31,Q={}.hasOwnProperty,G=[],Y=G.pop,J=G.push,K=G.push,Z=G.slice,ee=function(e,t){for(var n=0,r=e.length;r>n;n++)if(e[n]===t)return n;return-1},te="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",ne="[\\x20\\t\\r\\n\\f]",re="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",ie="\\["+ne+"*("+re+")(?:"+ne+"*([*^$|!~]?=)"+ne+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+re+"))|)"+ne+"*\\]",oe=":("+re+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+ie+")*)|.*)\\)|)",ae=new RegExp(ne+"+","g"),se=new RegExp("^"+ne+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ne+"+$","g"),ue=new RegExp("^"+ne+"*,"+ne+"*"),le=new RegExp("^"+ne+"*([>+~]|"+ne+")"+ne+"*"),ce=new RegExp("="+ne+"*([^\\]'\"]*?)"+ne+"*\\]","g"),fe=new RegExp(oe),pe=new RegExp("^"+re+"$"),de={ID:new RegExp("^#("+re+")"),CLASS:new RegExp("^\\.("+re+")"),TAG:new RegExp("^("+re+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ne+"*(even|odd|(([+-]|)(\\d*)n|)"+ne+"*(?:([+-]|)"+ne+"*(\\d+)|))"+ne+"*\\)|)","i"),bool:new RegExp("^(?:"+te+")$","i"),needsContext:new RegExp("^"+ne+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ne+"*((?:-\\d)?\\d*)"+ne+"*\\)|)(?=[^-]|$)","i")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\d$/i,me=/^[^{]+\{\s*\[native \w/,ve=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ye=/[+~]/,xe=/'|\\/g,we=new RegExp("\\\\([\\da-f]{1,6}"+ne+"?|("+ne+")|.)","ig"),be=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){q()};try{K.apply(G=Z.call($.childNodes),$.childNodes),G[$.childNodes.length].nodeType}catch(Ce){K={apply:G.length?function(e,t){J.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}b=t.support={},S=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},q=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:$;return r!==L&&9===r.nodeType&&r.documentElement?(L=r,H=L.documentElement,P=!S(L),(n=L.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Te,!1):n.attachEvent&&n.attachEvent("onunload",Te)),b.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByTagName=i(function(e){return e.appendChild(L.createComment("")),!e.getElementsByTagName("*").length}),b.getElementsByClassName=me.test(L.getElementsByClassName),b.getById=i(function(e){return H.appendChild(e).id=M,!L.getElementsByName||!L.getElementsByName(M).length}),b.getById?(T.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&P){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(we,be);return function(e){return e.getAttribute("id")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(we,be);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),T.find.TAG=b.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):b.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=b.getElementsByClassName&&function(e,t){return"undefined"!=typeof t.getElementsByClassName&&P?t.getElementsByClassName(e):void 0},F=[],O=[],(b.qsa=me.test(L.querySelectorAll))&&(i(function(e){H.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&O.push("[*^$]="+ne+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||O.push("\\["+ne+"*(?:value|"+te+")"),e.querySelectorAll("[id~="+M+"-]").length||O.push("~="),e.querySelectorAll(":checked").length||O.push(":checked"),e.querySelectorAll("a#"+M+"+*").length||O.push(".#.+[+~]")}),i(function(e){var t=L.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&O.push("name"+ne+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||O.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),O.push(",.*:")})),(b.matchesSelector=me.test(R=H.matches||H.webkitMatchesSelector||H.mozMatchesSelector||H.oMatchesSelector||H.msMatchesSelector))&&i(function(e){b.disconnectedMatch=R.call(e,"div"),R.call(e,"[s!='']:x"),F.push("!=",oe)}),O=O.length&&new RegExp(O.join("|")),F=F.length&&new RegExp(F.join("|")),t=me.test(H.compareDocumentPosition),W=t||me.test(H.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return D=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!b.sortDetached&&t.compareDocumentPosition(e)===n?e===L||e.ownerDocument===$&&W($,e)?-1:t===L||t.ownerDocument===$&&W($,t)?1:j?ee(j,e)-ee(j,t):0:4&n?-1:1)}:function(e,t){if(e===t)return D=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===L?-1:t===L?1:i?-1:o?1:j?ee(j,e)-ee(j,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===$?-1:u[r]===$?1:0},L):L},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==L&&q(e),n=n.replace(ce,"='$1']"),b.matchesSelector&&P&&!X[n+" "]&&(!F||!F.test(n))&&(!O||!O.test(n)))try{var r=R.call(e,n);if(r||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,L,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==L&&q(e),W(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==L&&q(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Q.call(T.attrHandle,t.toLowerCase())?n(e,t,!P):void 0;return void 0!==r?r:b.attributes||!P?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(D=!b.detectDuplicates,j=!b.sortStable&&e.slice(0),e.sort(U),D){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return j=null,e},C=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:de,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(we,be),e[3]=(e[3]||e[4]||e[5]||"").replace(we,be),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return de.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&fe.test(n)&&(t=k(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(we,be).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=B[e+" "];return t||(t=new RegExp("(^|"+ne+")"+e+"("+ne+"|$)"))&&B(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:n?(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ae," ")+" ").indexOf(r)>-1:"|="===n?o===r||o.slice(0,r.length+1)===r+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,v=s&&t.nodeName.toLowerCase(),y=!u&&!s,x=!1;if(m){if(o){for(;g;){for(p=t;p=p[g];)if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&y){for(p=m,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===z&&l[1],x=d&&l[2],p=d&&m.childNodes[d];p=++d&&p&&p[g]||(x=d=0)||h.pop();)if(1===p.nodeType&&++x&&p===t){c[e]=[z,d,x];break}}else if(y&&(p=t,f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),l=c[e]||[],d=l[0]===z&&l[1],x=d),x===!1)for(;(p=++d&&p&&p[g]||(x=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==v:1!==p.nodeType)||!++x||(y&&(f=p[M]||(p[M]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),c[e]=[z,x]),p!==t)););return x-=i,x===r||x%r===0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=T.pseudos[e]||T.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[M]?o(n):o.length>1?(i=[e,e,"",n],T.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=ee(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=E(e.replace(se,"$1"));return i[M]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(we,be),function(t){return(t.textContent||t.innerText||C(t)).indexOf(e)>-1}}),lang:r(function(e){return pe.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(we,be).toLowerCase(),function(t){var n;do if(n=P?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===H},focus:function(e){return e===L.activeElement&&(!L.hasFocus||L.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.pseudos.empty(e)},header:function(e){return ge.test(e.nodeName)},input:function(e){return he.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:l(function(){return[0]}),last:l(function(e,t){return[t-1]}),eq:l(function(e,t,n){return[0>n?n+t:n]}),even:l(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:l(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:l(function(e,t,n){for(var r=0>n?n+t:n;--r>=0;)e.push(r);return e}),gt:l(function(e,t,n){for(var r=0>n?n+t:n;++r2&&"ID"===(a=o[0]).type&&b.getById&&9===t.nodeType&&P&&T.relative[o[1].type]){if(t=(T.find.ID(a.matches[0].replace(we,be),t)||[])[0],!t)return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=de.needsContext.test(e)?0:o.length;i--&&(a=o[i],!T.relative[s=a.type]);)if((u=T.find[s])&&(r=u(a.matches[0].replace(we,be),ye.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),e=r.length&&p(o),!e)return K.apply(n,r),n;break}}return(l||E(e,f))(r,t,!P,n,!t||ye.test(e)&&c(t.parentNode)||t),n},b.sortStable=M.split("").sort(U).join("")===M,b.detectDuplicates=!!D,q(),b.sortDetached=i(function(e){return 1&e.compareDocumentPosition(L.createElement("div"))}),i(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){return n?void 0:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),b.attributes&&i(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?void 0:e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(te,function(e,t,n){var r;return n?void 0:e[t]===!0?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);oe.find=ce,oe.expr=ce.selectors,oe.expr[":"]=oe.expr.pseudos,oe.uniqueSort=oe.unique=ce.uniqueSort,oe.text=ce.getText,oe.isXMLDoc=ce.isXML,oe.contains=ce.contains;var fe=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&oe(e).is(n))break;r.push(e)}return r},pe=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},de=oe.expr.match.needsContext,he=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,ge=/^.[^:#\[\.,]*$/;oe.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?oe.find.matchesSelector(r,e)?[r]:[]:oe.find.matches(e,oe.grep(t,function(e){return 1===e.nodeType}))},oe.fn.extend({find:function(e){var t,n=this.length,r=[],i=this;if("string"!=typeof e)return this.pushStack(oe(e).filter(function(){for(t=0;n>t;t++)if(oe.contains(i[t],this))return!0}));for(t=0;n>t;t++)oe.find(e,i[t],r);return r=this.pushStack(n>1?oe.unique(r):r),r.selector=this.selector?this.selector+" "+e:e,r},filter:function(e){return this.pushStack(r(this,e||[],!1))},not:function(e){return this.pushStack(r(this,e||[],!0))},is:function(e){return!!r(this,"string"==typeof e&&de.test(e)?oe(e):e||[],!1).length}});var me,ve=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,ye=oe.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||me,"string"==typeof e){if(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:ve.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof oe?t[0]:t,oe.merge(this,oe.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:G,!0)),he.test(r[1])&&oe.isPlainObject(t))for(r in t)oe.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=G.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=G,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):oe.isFunction(e)?void 0!==n.ready?n.ready(e):e(oe):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),oe.makeArray(e,this))};ye.prototype=oe.fn,me=oe(G);var xe=/^(?:parents|prev(?:Until|All))/,we={children:!0,contents:!0,next:!0,prev:!0};oe.fn.extend({has:function(e){var t=oe(e,this),n=t.length;return this.filter(function(){for(var e=0;n>e;e++)if(oe.contains(this,t[e]))return!0})},closest:function(e,t){for(var n,r=0,i=this.length,o=[],a=de.test(e)||"string"!=typeof e?oe(e,t||this.context):0;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&oe.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?oe.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?Z.call(oe(e),this[0]):Z.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(oe.uniqueSort(oe.merge(this.get(),oe(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),oe.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return fe(e,"parentNode")},parentsUntil:function(e,t,n){return fe(e,"parentNode",n)},next:function(e){return i(e,"nextSibling")},prev:function(e){return i(e,"previousSibling")},nextAll:function(e){return fe(e,"nextSibling")},prevAll:function(e){return fe(e,"previousSibling")},nextUntil:function(e,t,n){return fe(e,"nextSibling",n)},prevUntil:function(e,t,n){return fe(e,"previousSibling",n)},siblings:function(e){return pe((e.parentNode||{}).firstChild,e)},children:function(e){return pe(e.firstChild)},contents:function(e){return e.contentDocument||oe.merge([],e.childNodes)}},function(e,t){oe.fn[e]=function(n,r){var i=oe.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=oe.filter(r,i)),this.length>1&&(we[e]||oe.uniqueSort(i),xe.test(e)&&i.reverse()),this.pushStack(i)}});var be=/\S+/g;oe.Callbacks=function(e){e="string"==typeof e?o(e):oe.extend({},e);var t,n,r,i,a=[],s=[],u=-1,l=function(){for(i=e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u-1;)a.splice(n,1),u>=n&&u--}),this},has:function(e){return e?oe.inArray(e,a)>-1:a.length>0},empty:function(){return a&&(a=[]),this},disable:function(){return i=s=[],a=n="",this},disabled:function(){return!a},lock:function(){return i=s=[],n||(a=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},oe.extend({Deferred:function(e){var t=[["resolve","done",oe.Callbacks("once memory"),"resolved"],["reject","fail",oe.Callbacks("once memory"),"rejected"],["notify","progress",oe.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return oe.Deferred(function(n){oe.each(t,function(t,o){var a=oe.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&oe.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?oe.extend(e,r):r}},i={};return r.pipe=r.then,oe.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=Y.call(arguments),a=o.length,s=1!==a||e&&oe.isFunction(e.promise)?a:0,u=1===s?e:oe.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?Y.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);a>i;i++)o[i]&&oe.isFunction(o[i].promise)?o[i].promise().progress(l(i,n,t)).done(l(i,r,o)).fail(u.reject):--s;return s||u.resolveWith(r,o),u.promise()}});var Te;oe.fn.ready=function(e){return oe.ready.promise().done(e),this},oe.extend({isReady:!1,readyWait:1,holdReady:function(e){e?oe.readyWait++:oe.ready(!0)},ready:function(e){(e===!0?--oe.readyWait:oe.isReady)||(oe.isReady=!0,e!==!0&&--oe.readyWait>0||(Te.resolveWith(G,[oe]),oe.fn.triggerHandler&&(oe(G).triggerHandler("ready"),oe(G).off("ready"))))}}),oe.ready.promise=function(t){return Te||(Te=oe.Deferred(),"complete"===G.readyState||"loading"!==G.readyState&&!G.documentElement.doScroll?e.setTimeout(oe.ready):(G.addEventListener("DOMContentLoaded",a),e.addEventListener("load",a))),Te.promise(t)},oe.ready.promise();var Ce=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===oe.type(n)){i=!0;for(s in n)Ce(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,oe.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(oe(e),n)})),t))for(;u>s;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},Se=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};s.uid=1,s.prototype={register:function(e,t){var n=t||{};return e.nodeType?e[this.expando]=n:Object.defineProperty(e,this.expando,{value:n,writable:!0,configurable:!0}),e[this.expando]},cache:function(e){if(!Se(e))return{};var t=e[this.expando];return t||(t={},Se(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[t]=n;else for(r in t)i[r]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][t]},access:function(e,t,n){var r;return void 0===t||t&&"string"==typeof t&&void 0===n?(r=this.get(e,t),void 0!==r?r:this.get(e,oe.camelCase(t))):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r,i,o=e[this.expando];if(void 0!==o){if(void 0===t)this.register(e);else{oe.isArray(t)?r=t.concat(t.map(oe.camelCase)):(i=oe.camelCase(t),t in o?r=[t,i]:(r=i,r=r in o?[r]:r.match(be)||[])),n=r.length;for(;n--;)delete o[r[n]]}(void 0===t||oe.isEmptyObject(o))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!oe.isEmptyObject(t)}};var ke=new s,Ee=new s,Ae=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Ne=/[A-Z]/g;oe.extend({hasData:function(e){return Ee.hasData(e)||ke.hasData(e)},data:function(e,t,n){return Ee.access(e,t,n)},removeData:function(e,t){Ee.remove(e,t)},_data:function(e,t,n){return ke.access(e,t,n)},_removeData:function(e,t){ke.remove(e,t)}}),oe.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=Ee.get(o),1===o.nodeType&&!ke.get(o,"hasDataAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=oe.camelCase(r.slice(5)),u(o,r,i[r])));ke.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){Ee.set(this,e)}):Ce(this,function(t){var n,r;if(o&&void 0===t){if(n=Ee.get(o,e)||Ee.get(o,e.replace(Ne,"-$&").toLowerCase()),void 0!==n)return n;if(r=oe.camelCase(e),n=Ee.get(o,r),void 0!==n)return n;if(n=u(o,r,void 0),void 0!==n)return n}else r=oe.camelCase(e),this.each(function(){var n=Ee.get(this,r);Ee.set(this,r,t),e.indexOf("-")>-1&&void 0!==n&&Ee.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){Ee.remove(this,e)})}}),oe.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=ke.get(e,t),n&&(!r||oe.isArray(n)?r=ke.access(e,t,oe.makeArray(n)):r.push(n)),r||[]):void 0},dequeue:function(e,t){t=t||"fx";var n=oe.queue(e,t),r=n.length,i=n.shift(),o=oe._queueHooks(e,t),a=function(){oe.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return ke.get(e,n)||ke.access(e,n,{empty:oe.Callbacks("once memory").add(function(){ke.remove(e,[t+"queue",n])})})}}),oe.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length",""],thead:[1,"