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. - -Introduction to the Physical 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 @@ - - - - - - - - - - - - - - - - - - +

+
+ +
+
+
+

+Getting started guide

+

Reach out to physical-web-discuss@googlegroups.com with any questions.

+ +

+Who this applies to

+

This guide is meant to be a tutorial for developers who are interested in the Physical Web and would like to deploy it to their users.

+ +

+Introduction

+

To distribute your web page over the Physical Web, you need two things: (1) a bluetooth low energy beacon (the backbone of the Physical Web) and (2) a URL. Depending on how beacons are configured and how many are used, various deployment options are possible. This guide will walk through these different options -- starting from a basic single beacon deployment and transitioning into a more complex multi-beacon, multi-URL deployment.

+ +

+Bluetooth Low Energy (BLE) Beacons

+

BLE beacons are low powered devices that unidirectionally broadcast data for applications and devices to use. The Physical Web is built on top of beacons that broadcast over the Eddystone protocol. You can find a list of Eddystone-supported beacons here for your deployment.

+

When you purchase your beacons, the manufacturer will recommend an application that can connect to the beacons and configure them. Below are the major configuration parameters that you can change for each beacon.

+
+

+URL

+

This is the link to your webpage. The Physical Web on Chrome and Nearby Notifications only supports HTTPS URLs. We recommend the URLs to have the following attributes to provide the best possible user experience:

+
    +
  • Mobile-optimized, both for the user interface and navigation.
  • +
  • Single-purpose. Each should should cater to a single action (e.g. consume a piece of content, perform an action). Remember that users won’t be browsing to this page but instead will be arriving directly to it if they are nearby your beacon.
  • +
+

The Eddystone protocol provides 17 bytes for the URL packet, so you will likely need a URL shortener to compress the byte size of your URL if the beacon configuration application doesn’t automatically have one. If you anticipate needing to change your broadcast URLs frequently over time, consider using a URL shortener that allows you to update the end URLs later.

+
+
+

+Transmission Power

+

Beacons can vary their transmission power, typically from -40dbm to +4dbm. Note that power is a rough proxy for the transmission distance, which is ultimately a function of beacon antenna, phone antenna, beacon placement, and more. Our suggestions below are therefore rough guidelines.

+

In general, we recommend setting the transmission power to be low, typically between -30dbm and -35dbm.

+

This has 2 advantages:

+
    +
  • It prolongs the beacon’s battery life.
  • +
  • It ensures that users see your broadcast URL only when they are close to your device (thereby making your content more contextual).
  • +
+

However, there are certain situations where a higher transmission power may be desired such as covering a large space with a single URL.

+
+
+

+Advertising Frequency

+

As a general rule, we recommend an advertising frequency of 700ms. +

+

Most beacons can advertise in a range from as fast as every 100ms to as slow as every 10 seconds. Going slower can save battery life but scanning applications such as Chrome and Nearby Notifications may not reliably surface URLs to users if the frequency is much slower than 1 second. Going faster than 700ms is possible too, although this will reduce the beacon's battery life.

+
+ +

+Deploying beacons

+

The complexity of your deployment will depend on how many beacons you’ll deploy and how many distinct URLs you plan to broadcast. Below, we summarize the different deployment options and which use cases they typically pertain to.

+
+ Deploying Beacons Infographic +
+

+1 Beacon, 1 Url

+

This is the simplest deployment option to broadcast a single web page associated with a particular location or object. For example, a beacon could be placed on a movie poster and configured to broadcast the movie trailer.

+

For best performance, we recommend the following best practices:

+
    +
  • Place the beacon at high altitude. There will be less interference, and it will be less likely to be tampered with.
  • +
  • Avoid placing the beacon inside or behind metal. Bluetooth signals do not travel well through metal.
  • +
+

+Many beacons, 1 URL

+

If you have a large space in which you’d like to broadcast a single URL, you might consider using multiple beacons. For example, multiple beacons could be used at a conference to broadcast a link to the schedule.

+

A few things to note for this type of deployment:

+
    +
  • Consider increasing the transmit power on each beacon to cover a broader area. This enables you to use fewer beacons to cover the entire space.
  • +
  • For Physical Web scanners such as Chrome, identical URLs will be de-duplicated before being displayed to the user. For example, if five beacons in a conference are all broadcasting the schedule URL, only a single schedule URL will be displayed to users. This enables you to place multiple beacons broadcasting the same URL in an area without overwhelming users with multiple results.
  • +
+

+Many beacons, many URLs

+

You might want to associate unique URLs with different locations in your space. For example, each exhibit at a museum could have a different URL associated with it. In this scenario, you would deploy multiple beacons that broadcast distinct URLs.

+

You’d likely want to restrict the range in which users can discover each URL -- in the museum example above, users would likely want to discover exhibit URLs when they are within close vicinity of the exhibit. To do this, consider using a lower transmission power for each beacon you deploy in this environment.

+

+Managing content

+

For certain deployments, you might want to change the broadcast URLs after initial configuration. One example is a large retailer that has deployed beacons across a variety of stores and wants to continually update the broadcast URLs regularly to reflect new content. While it is possible for you to continually re-configure the beacons with a new URL, it requires you to be within bluetooth broadcast range.

+ +

For larger beacon deployments, we recommend using a URL shortener that enables you to later edit the destination URL. Many Eddystone certified beacon manufacturers include their own URL management solutions. In addition, we’ve included below a few well known URL shorteners that offer editing of the destination URL -- note that it is not meant to be an exhaustive list:

+ +

Alternately, you can choose to build your own updatable URL shortener. Below are a few example github repositories with basic URL shortener implementations that can be further customized. As before, this is not meant to be an exhaustive list. They can be used as building blocks:

+ +

Finally, you may wish to disable your URL broadcast in certain scenarios (e.g. turning off exhibit information for a temporary exhibit at a museum). To do so, you can update your shortened URL to redirect to a 404 page. Physical Web on Chrome and Nearby Notifications will not display these URLs.

+
+
+
+
+
+ + + + + + diff --git a/index.html b/index.html new file mode 100644 index 00000000..e6dc7657 --- /dev/null +++ b/index.html @@ -0,0 +1,674 @@ + + + + + + + + + The Physical Web + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

The Physical Web is an open approach to enable quick and seamless interactions with physical objects and locations.

+
+
+
+
+
+
+
+ + + + + background + + + + Layer 1 + + + + + + PAID + + + PAY + + + + + 00:00 + + + + 00:10 + + + + 00:20 + + + + 00:30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 00:00 + + + + 00:10 + + + + 00:20 + + + + 00:30 + + + + + +
+
+
+
+

Everything is a tap away

+

Walk up and interact with any object -- a parking meter, a toy, a poster -- or location -- a bus stop, a museum, a store -- without installing an app first. Interactions are only a tap away.

+
+
+
+
+
+
+
+
+ + + + + background + + + + Layer 1 + + + + + + + + + + + + + + + Spot + + + + Call Humane + Society + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

See what’s useful around you

+

See web pages associated with the space around you. Choose the page most useful to you.

+
+
+
+
+
+
+
+
+ + + + + background + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

Any object or place can broadcast content

+

When anything can offer information and utility, the possiblities are endless.

+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

How does this work?

+

The Physical Web enables you to see a list of URLs being broadcast by objects in the environment around you. Any object can be embedded with a Bluetooth Low Energy (BLE) beacon, which is a low powered, battery efficient device that broadcasts content over bluetooth. Beacons that support the Eddystone protocol specification can broadcast URLs. Services on your device such as Google Chrome or Nearby Notifications can scan for and display these URLs after passing them through a proxy.

+
+
+
+
+
+
+
+
+

Explore the Physical Web in 3 easy steps.

+

Get beacons. On supported Android devices, you can use Beacon Toy to transform your phone into an Eddystone beacon. Otherwise, choose from a variety of beacon manufacturers.

+

Configure beacons. You’ll have to select which URLs you’d like to broadcast (browsers such as Chrome and Nearby Notifications only support HTTPS) and how far and often you want your beacons to broadcast.

+

Deploy. Place your beacons in a physical space. Anyone who passes by with a Physical Web-compatible service will see your URL.

+ +
+
+
+
+ + + + diff --git a/ios/PhyWeb.xcodeproj/project.pbxproj b/ios/PhyWeb.xcodeproj/project.pbxproj deleted file mode 100644 index 625eba33..00000000 --- a/ios/PhyWeb.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1096 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - BD039C0419D0E8F8005A7F37 /* libMBProgressHUD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BD039BFF19D0E8BB005A7F37 /* libMBProgressHUD.a */; }; - BD039C0519D0E8F8005A7F37 /* libSDWebImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE9F27619ABE6DE00AA8DFB /* libSDWebImage.a */; }; - BD3E51B91B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BD3E51B81B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.m */; }; - BD49959019F7178E000945BC /* libSDWebImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE9F27619ABE6DE00AA8DFB /* libSDWebImage.a */; }; - BD49959219F717C6000945BC /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE9F28519ABF02700AA8DFB /* ImageIO.framework */; }; - BD54189E1BF15FA300F175DB /* NSString+UB.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418891BF15FA300F175DB /* NSString+UB.m */; settings = {ASSET_TAGS = (); }; }; - BD54189F1BF15FA300F175DB /* NSURL+UB.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54188B1BF15FA300F175DB /* NSURL+UB.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A01BF15FA300F175DB /* UBConfigurableUriBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54188D1BF15FA300F175DB /* UBConfigurableUriBeacon.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A11BF15FA300F175DB /* UBUriBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418901BF15FA300F175DB /* UBUriBeacon.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A21BF15FA300F175DB /* UBUriBeaconReader.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418931BF15FA300F175DB /* UBUriBeaconReader.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A31BF15FA300F175DB /* UBUriBeaconScanner.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418951BF15FA300F175DB /* UBUriBeaconScanner.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A41BF15FA300F175DB /* UBUriBeaconWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418981BF15FA300F175DB /* UBUriBeaconWriter.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A51BF15FA300F175DB /* UBUriReader.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54189A1BF15FA300F175DB /* UBUriReader.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A61BF15FA300F175DB /* UBUriWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54189C1BF15FA300F175DB /* UBUriWriter.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A71BF15FCF00F175DB /* NSString+UB.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418891BF15FA300F175DB /* NSString+UB.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A81BF15FCF00F175DB /* NSURL+UB.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54188B1BF15FA300F175DB /* NSURL+UB.m */; settings = {ASSET_TAGS = (); }; }; - BD5418A91BF15FCF00F175DB /* UBConfigurableUriBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54188D1BF15FA300F175DB /* UBConfigurableUriBeacon.m */; settings = {ASSET_TAGS = (); }; }; - BD5418AA1BF15FCF00F175DB /* UBUriBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418901BF15FA300F175DB /* UBUriBeacon.m */; settings = {ASSET_TAGS = (); }; }; - BD5418AB1BF15FCF00F175DB /* UBUriBeaconReader.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418931BF15FA300F175DB /* UBUriBeaconReader.m */; settings = {ASSET_TAGS = (); }; }; - BD5418AC1BF15FCF00F175DB /* UBUriBeaconScanner.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418951BF15FA300F175DB /* UBUriBeaconScanner.m */; settings = {ASSET_TAGS = (); }; }; - BD5418AD1BF15FCF00F175DB /* UBUriBeaconWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = BD5418981BF15FA300F175DB /* UBUriBeaconWriter.m */; settings = {ASSET_TAGS = (); }; }; - BD5418AE1BF15FCF00F175DB /* UBUriReader.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54189A1BF15FA300F175DB /* UBUriReader.m */; settings = {ASSET_TAGS = (); }; }; - BD5418B01BF1613D00F175DB /* UBUriWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = BD54189C1BF15FA300F175DB /* UBUriWriter.m */; settings = {ASSET_TAGS = (); }; }; - BDA3FA8919EF2DD700CFAA74 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3FA8819EF2DD700CFAA74 /* NotificationCenter.framework */; }; - BDA3FA8F19EF2DD700CFAA74 /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA3FA8E19EF2DD700CFAA74 /* TodayViewController.m */; }; - BDA3FA9419EF2DD700CFAA74 /* today.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BDA3FA8619EF2DD700CFAA74 /* today.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - BDA3FAA319EF329400CFAA74 /* PWBeaconManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1D919A7EAFC00AA8DFB /* PWBeaconManager.m */; }; - BDA3FAA419EF329700CFAA74 /* PWMetadataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1B119A7E46D00AA8DFB /* PWMetadataRequest.m */; }; - BDA3FAA519EF329900CFAA74 /* PWBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1DF19A7EF3F00AA8DFB /* PWBeacon.m */; }; - BDA3FAA619EF329C00CFAA74 /* PWURLShortener.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1E219A7FB4000AA8DFB /* PWURLShortener.m */; }; - BDA3FAA719EF338F00CFAA74 /* PWBeaconCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1B819A7E46D00AA8DFB /* PWBeaconCell.m */; }; - BDA3FAA819EF38A600CFAA74 /* PWSignalStrengthView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1C219A7E46D00AA8DFB /* PWSignalStrengthView.m */; }; - BDBFD6731A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = BDBFD6701A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.m */; }; - BDBFD6741A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = BDBFD6721A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.m */; }; - BDC5A9AC1B3B3A90008D9A0B /* PWChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDC5A9AB1B3B3A90008D9A0B /* PWChartViewController.m */; }; - BDDC71081B4B20C20000581C /* JBChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDDC71051B4B20C20000581C /* JBChartView.m */; }; - BDDC71091B4B20C20000581C /* JBLineChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDDC71071B4B20C20000581C /* JBLineChartView.m */; }; - BDE4D8A619DE0242007938F0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8A519DE0242007938F0 /* LaunchScreen.xib */; }; - BDE4D8A819DE03F1007938F0 /* LaunchIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8A719DE03F1007938F0 /* LaunchIcon.png */; }; - BDE4D8B419DE154B007938F0 /* PWPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE4D8B319DE154B007938F0 /* PWPlaceholderView.m */; }; - BDE4D8B819DF65A4007938F0 /* Progress1.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8B519DF65A4007938F0 /* Progress1.png */; }; - BDE4D8B919DF65A4007938F0 /* Progress2.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8B619DF65A4007938F0 /* Progress2.png */; }; - BDE4D8BA19DF65A4007938F0 /* Progress3.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8B719DF65A4007938F0 /* Progress3.png */; }; - BDE4D8BD19DF65BE007938F0 /* PWActivityIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE4D8BC19DF65BE007938F0 /* PWActivityIndicator.m */; }; - BDE4D8C819E355B4007938F0 /* ScanError.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE4D8C719E355B4007938F0 /* ScanError.png */; }; - BDE9F18E19A7CDF100AA8DFB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F18D19A7CDF100AA8DFB /* main.m */; }; - BDE9F19119A7CDF100AA8DFB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F19019A7CDF100AA8DFB /* AppDelegate.m */; }; - BDE9F19919A7CDF100AA8DFB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BDE9F19819A7CDF100AA8DFB /* Images.xcassets */; }; - BDE9F1C319A7E46D00AA8DFB /* PWMetadataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1B119A7E46D00AA8DFB /* PWMetadataRequest.m */; }; - BDE9F1C719A7E46D00AA8DFB /* PWBeaconCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1B819A7E46D00AA8DFB /* PWBeaconCell.m */; }; - BDE9F1C819A7E46D00AA8DFB /* PWBeaconsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1BA19A7E46D00AA8DFB /* PWBeaconsViewController.m */; }; - BDE9F1C919A7E46D00AA8DFB /* PWGradientView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1BC19A7E46D00AA8DFB /* PWGradientView.m */; }; - BDE9F1CC19A7E46D00AA8DFB /* PWSignalStrengthView.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1C219A7E46D00AA8DFB /* PWSignalStrengthView.m */; }; - BDE9F1DA19A7EAFC00AA8DFB /* PWBeaconManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1D919A7EAFC00AA8DFB /* PWBeaconManager.m */; }; - BDE9F1E019A7EF3F00AA8DFB /* PWBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1DF19A7EF3F00AA8DFB /* PWBeacon.m */; }; - BDE9F1E319A7FB4000AA8DFB /* PWURLShortener.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F1E219A7FB4000AA8DFB /* PWURLShortener.m */; }; - BDE9F28019ABEF4D00AA8DFB /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE9F27F19ABEF4D00AA8DFB /* CoreBluetooth.framework */; }; - BDE9F28619ABF02700AA8DFB /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDE9F28519ABF02700AA8DFB /* ImageIO.framework */; }; - BDE9F29A19AD1D5000AA8DFB /* gear.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE9F29819AD1D5000AA8DFB /* gear.png */; }; - BDE9F29B19AD1D5000AA8DFB /* gear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE9F29919AD1D5000AA8DFB /* gear@2x.png */; }; - BDE9F2A019AD551500AA8DFB /* PWConfigureViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F29D19AD551500AA8DFB /* PWConfigureViewController.m */; }; - BDE9F2A119AD551500AA8DFB /* PWSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE9F29F19AD551500AA8DFB /* PWSettingsViewController.m */; }; - BDE9F2AE19AEA58200AA8DFB /* 37x-Checkmark.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE9F2AC19AEA58200AA8DFB /* 37x-Checkmark.png */; }; - BDE9F2AF19AEA58200AA8DFB /* 37x-Checkmark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BDE9F2AD19AEA58200AA8DFB /* 37x-Checkmark@2x.png */; }; - BDFC4DD019E60C1A00DC381C /* licenses.html in Resources */ = {isa = PBXBuildFile; fileRef = BDFC4DCF19E60C1A00DC381C /* licenses.html */; }; - BDFC4DD819E60C5200DC381C /* PWSimpleWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BDFC4DD719E60C5200DC381C /* PWSimpleWebViewController.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - BD039BFE19D0E8BB005A7F37 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BD039BFA19D0E8BB005A7F37 /* MBProgressHUD.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = D286A7491518C70F00E13FB8; - remoteInfo = MBProgressHUD; - }; - BD039C0219D0E8DE005A7F37 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BD039BFA19D0E8BB005A7F37 /* MBProgressHUD.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = D286A7481518C70F00E13FB8; - remoteInfo = MBProgressHUD; - }; - BD5418851BF15F7200F175DB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 4A2CADFF1AB4BB5300B6BC39; - remoteInfo = WebImage; - }; - BDA3FA9219EF2DD700CFAA74 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F18019A7CDF100AA8DFB /* Project object */; - proxyType = 1; - remoteGlobalIDString = BDA3FA8519EF2DD700CFAA74; - remoteInfo = today; - }; - BDA3FA9519EF2DD700CFAA74 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F18019A7CDF100AA8DFB /* Project object */; - proxyType = 1; - remoteGlobalIDString = BDA3FA8519EF2DD700CFAA74; - remoteInfo = today; - }; - BDE9F27519ABE6DE00AA8DFB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 53761325155AD0D5005750A4; - remoteInfo = SDWebImage; - }; - BDE9F27719ABE6DE00AA8DFB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 537D95C117ECC1FE0097C263; - remoteInfo = "SDWebImage+WebP"; - }; - BDE9F27919ABE6DE00AA8DFB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 531041E0157EAFA400BBABC3; - remoteInfo = "SDWebImage+MKAnnotation"; - }; - BDE9F27B19ABE6EC00AA8DFB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 53761307155AD0D5005750A4; - remoteInfo = SDWebImage; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - BDA3FA9F19EF2DD700CFAA74 /* Embed App Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - BDA3FA9419EF2DD700CFAA74 /* today.appex in Embed App Extensions */, - ); - name = "Embed App Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - BD039BFA19D0E8BB005A7F37 /* MBProgressHUD.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MBProgressHUD.xcodeproj; path = "third-party/MBProgressHUD/MBProgressHUD.xcodeproj"; sourceTree = ""; }; - BD3E51B71B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWBeaconChartTableViewCell.h; sourceTree = ""; }; - BD3E51B81B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWBeaconChartTableViewCell.m; sourceTree = ""; }; - BD5418881BF15FA300F175DB /* NSString+UB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UB.h"; sourceTree = ""; }; - BD5418891BF15FA300F175DB /* NSString+UB.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UB.m"; sourceTree = ""; }; - BD54188A1BF15FA300F175DB /* NSURL+UB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+UB.h"; sourceTree = ""; }; - BD54188B1BF15FA300F175DB /* NSURL+UB.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+UB.m"; sourceTree = ""; }; - BD54188C1BF15FA300F175DB /* UBConfigurableUriBeacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBConfigurableUriBeacon.h; sourceTree = ""; }; - BD54188D1BF15FA300F175DB /* UBConfigurableUriBeacon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBConfigurableUriBeacon.m; sourceTree = ""; }; - BD54188E1BF15FA300F175DB /* UBConfigurableURIBeaconPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBConfigurableURIBeaconPrivate.h; sourceTree = ""; }; - BD54188F1BF15FA300F175DB /* UBUriBeacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeacon.h; sourceTree = ""; }; - BD5418901BF15FA300F175DB /* UBUriBeacon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriBeacon.m; sourceTree = ""; }; - BD5418911BF15FA300F175DB /* UBUriBeaconPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeaconPrivate.h; sourceTree = ""; }; - BD5418921BF15FA300F175DB /* UBUriBeaconReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeaconReader.h; sourceTree = ""; }; - BD5418931BF15FA300F175DB /* UBUriBeaconReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriBeaconReader.m; sourceTree = ""; }; - BD5418941BF15FA300F175DB /* UBUriBeaconScanner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeaconScanner.h; sourceTree = ""; }; - BD5418951BF15FA300F175DB /* UBUriBeaconScanner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriBeaconScanner.m; sourceTree = ""; }; - BD5418961BF15FA300F175DB /* UBUriBeaconScannerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeaconScannerPrivate.h; sourceTree = ""; }; - BD5418971BF15FA300F175DB /* UBUriBeaconWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriBeaconWriter.h; sourceTree = ""; }; - BD5418981BF15FA300F175DB /* UBUriBeaconWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriBeaconWriter.m; sourceTree = ""; }; - BD5418991BF15FA300F175DB /* UBUriReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriReader.h; sourceTree = ""; }; - BD54189A1BF15FA300F175DB /* UBUriReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriReader.m; sourceTree = ""; }; - BD54189B1BF15FA300F175DB /* UBUriWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UBUriWriter.h; sourceTree = ""; }; - BD54189C1BF15FA300F175DB /* UBUriWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UBUriWriter.m; sourceTree = ""; }; - BD54189D1BF15FA300F175DB /* UriBeacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UriBeacon.h; sourceTree = ""; }; - BDA3FA8619EF2DD700CFAA74 /* today.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = today.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - BDA3FA8819EF2DD700CFAA74 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - BDA3FA8C19EF2DD700CFAA74 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BDA3FA8D19EF2DD700CFAA74 /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; - BDA3FA8E19EF2DD700CFAA74 /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; - BDBFD66E1A2D125700A0B5E0 /* SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVPullToRefresh.h; sourceTree = ""; }; - BDBFD66F1A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVInfiniteScrolling.h"; sourceTree = ""; }; - BDBFD6701A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVInfiniteScrolling.m"; sourceTree = ""; }; - BDBFD6711A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVPullToRefresh.h"; sourceTree = ""; }; - BDBFD6721A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVPullToRefresh.m"; sourceTree = ""; }; - BDC5A9AA1B3B3A90008D9A0B /* PWChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWChartViewController.h; sourceTree = ""; }; - BDC5A9AB1B3B3A90008D9A0B /* PWChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWChartViewController.m; sourceTree = ""; }; - BDDC71041B4B20C20000581C /* JBChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JBChartView.h; path = "third-party/JBChartView/JBChartView.h"; sourceTree = ""; }; - BDDC71051B4B20C20000581C /* JBChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JBChartView.m; path = "third-party/JBChartView/JBChartView.m"; sourceTree = ""; }; - BDDC71061B4B20C20000581C /* JBLineChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JBLineChartView.h; path = "third-party/JBChartView/JBLineChartView.h"; sourceTree = ""; }; - BDDC71071B4B20C20000581C /* JBLineChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JBLineChartView.m; path = "third-party/JBChartView/JBLineChartView.m"; sourceTree = ""; }; - BDDFE6ED1A0069B700A76255 /* PhyWeb.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PhyWeb.entitlements; sourceTree = ""; }; - BDDFE6EE1A0069F000A76255 /* today.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = today.entitlements; sourceTree = ""; }; - BDE4D88519DDF678007938F0 /* icon-29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-29.png"; sourceTree = ""; }; - BDE4D88619DDF678007938F0 /* icon-40.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-40.png"; sourceTree = ""; }; - BDE4D88719DDF678007938F0 /* icon-50.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-50.png"; sourceTree = ""; }; - BDE4D88819DDF678007938F0 /* icon-57.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-57.png"; sourceTree = ""; }; - BDE4D88919DDF678007938F0 /* icon-58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-58.png"; sourceTree = ""; }; - BDE4D88A19DDF678007938F0 /* icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-72.png"; sourceTree = ""; }; - BDE4D88B19DDF678007938F0 /* icon-76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-76.png"; sourceTree = ""; }; - BDE4D88C19DDF678007938F0 /* icon-80.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-80.png"; sourceTree = ""; }; - BDE4D88D19DDF678007938F0 /* icon-87.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-87.png"; sourceTree = ""; }; - BDE4D88E19DDF678007938F0 /* icon-100.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-100.png"; sourceTree = ""; }; - BDE4D88F19DDF678007938F0 /* icon-114.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-114.png"; sourceTree = ""; }; - BDE4D89019DDF678007938F0 /* icon-120.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-120.png"; sourceTree = ""; }; - BDE4D89119DDF678007938F0 /* icon-144.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-144.png"; sourceTree = ""; }; - BDE4D89219DDF678007938F0 /* icon-152.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-152.png"; sourceTree = ""; }; - BDE4D89319DDF678007938F0 /* icon-180.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-180.png"; sourceTree = ""; }; - BDE4D89419DDF678007938F0 /* icon-512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-512.png"; sourceTree = ""; }; - BDE4D8A519DE0242007938F0 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; - BDE4D8A719DE03F1007938F0 /* LaunchIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LaunchIcon.png; sourceTree = ""; }; - BDE4D8B219DE154B007938F0 /* PWPlaceholderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWPlaceholderView.h; sourceTree = ""; }; - BDE4D8B319DE154B007938F0 /* PWPlaceholderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWPlaceholderView.m; sourceTree = ""; }; - BDE4D8B519DF65A4007938F0 /* Progress1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Progress1.png; sourceTree = ""; }; - BDE4D8B619DF65A4007938F0 /* Progress2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Progress2.png; sourceTree = ""; }; - BDE4D8B719DF65A4007938F0 /* Progress3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Progress3.png; sourceTree = ""; }; - BDE4D8BB19DF65BE007938F0 /* PWActivityIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWActivityIndicator.h; sourceTree = ""; }; - BDE4D8BC19DF65BE007938F0 /* PWActivityIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWActivityIndicator.m; sourceTree = ""; }; - BDE4D8C719E355B4007938F0 /* ScanError.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ScanError.png; sourceTree = ""; }; - BDE9F18819A7CDF100AA8DFB /* PhyWeb.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhyWeb.app; sourceTree = BUILT_PRODUCTS_DIR; }; - BDE9F18C19A7CDF100AA8DFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BDE9F18D19A7CDF100AA8DFB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - BDE9F18F19A7CDF100AA8DFB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - BDE9F19019A7CDF100AA8DFB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - BDE9F19819A7CDF100AA8DFB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - BDE9F1B019A7E46D00AA8DFB /* PWMetadataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWMetadataRequest.h; sourceTree = ""; }; - BDE9F1B119A7E46D00AA8DFB /* PWMetadataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWMetadataRequest.m; sourceTree = ""; }; - BDE9F1B719A7E46D00AA8DFB /* PWBeaconCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWBeaconCell.h; sourceTree = ""; }; - BDE9F1B819A7E46D00AA8DFB /* PWBeaconCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWBeaconCell.m; sourceTree = ""; }; - BDE9F1B919A7E46D00AA8DFB /* PWBeaconsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWBeaconsViewController.h; sourceTree = ""; }; - BDE9F1BA19A7E46D00AA8DFB /* PWBeaconsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWBeaconsViewController.m; sourceTree = ""; }; - BDE9F1BB19A7E46D00AA8DFB /* PWGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWGradientView.h; sourceTree = ""; }; - BDE9F1BC19A7E46D00AA8DFB /* PWGradientView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWGradientView.m; sourceTree = ""; }; - BDE9F1C119A7E46D00AA8DFB /* PWSignalStrengthView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWSignalStrengthView.h; sourceTree = ""; }; - BDE9F1C219A7E46D00AA8DFB /* PWSignalStrengthView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWSignalStrengthView.m; sourceTree = ""; }; - BDE9F1D819A7EAFC00AA8DFB /* PWBeaconManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWBeaconManager.h; sourceTree = ""; }; - BDE9F1D919A7EAFC00AA8DFB /* PWBeaconManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWBeaconManager.m; sourceTree = ""; }; - BDE9F1DE19A7EF3F00AA8DFB /* PWBeacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWBeacon.h; sourceTree = ""; }; - BDE9F1DF19A7EF3F00AA8DFB /* PWBeacon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWBeacon.m; sourceTree = ""; }; - BDE9F1E119A7FB4000AA8DFB /* PWURLShortener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWURLShortener.h; sourceTree = ""; }; - BDE9F1E219A7FB4000AA8DFB /* PWURLShortener.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWURLShortener.m; sourceTree = ""; }; - BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SDWebImage.xcodeproj; path = "third-party/SDWebImage/SDWebImage.xcodeproj"; sourceTree = ""; }; - BDE9F27F19ABEF4D00AA8DFB /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; - BDE9F28119ABEF5300AA8DFB /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; - BDE9F28319ABEFB400AA8DFB /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - BDE9F28519ABF02700AA8DFB /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; - BDE9F29819AD1D5000AA8DFB /* gear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gear.png; sourceTree = ""; }; - BDE9F29919AD1D5000AA8DFB /* gear@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "gear@2x.png"; sourceTree = ""; }; - BDE9F29C19AD551500AA8DFB /* PWConfigureViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWConfigureViewController.h; sourceTree = ""; }; - BDE9F29D19AD551500AA8DFB /* PWConfigureViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWConfigureViewController.m; sourceTree = ""; }; - BDE9F29E19AD551500AA8DFB /* PWSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWSettingsViewController.h; sourceTree = ""; }; - BDE9F29F19AD551500AA8DFB /* PWSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWSettingsViewController.m; sourceTree = ""; }; - BDE9F2AC19AEA58200AA8DFB /* 37x-Checkmark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "37x-Checkmark.png"; sourceTree = ""; }; - BDE9F2AD19AEA58200AA8DFB /* 37x-Checkmark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "37x-Checkmark@2x.png"; sourceTree = ""; }; - BDFC4DCF19E60C1A00DC381C /* licenses.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = licenses.html; sourceTree = ""; }; - BDFC4DD619E60C5200DC381C /* PWSimpleWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PWSimpleWebViewController.h; sourceTree = ""; }; - BDFC4DD719E60C5200DC381C /* PWSimpleWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PWSimpleWebViewController.m; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - BDA3FA8319EF2DD700CFAA74 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BD49959219F717C6000945BC /* ImageIO.framework in Frameworks */, - BD49959019F7178E000945BC /* libSDWebImage.a in Frameworks */, - BDA3FA8919EF2DD700CFAA74 /* NotificationCenter.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDE9F18519A7CDF100AA8DFB /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - BD039C0419D0E8F8005A7F37 /* libMBProgressHUD.a in Frameworks */, - BD039C0519D0E8F8005A7F37 /* libSDWebImage.a in Frameworks */, - BDE9F28619ABF02700AA8DFB /* ImageIO.framework in Frameworks */, - BDE9F28019ABEF4D00AA8DFB /* CoreBluetooth.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - BD039BFB19D0E8BB005A7F37 /* Products */ = { - isa = PBXGroup; - children = ( - BD039BFF19D0E8BB005A7F37 /* libMBProgressHUD.a */, - ); - name = Products; - sourceTree = ""; - }; - BD5418871BF15FA300F175DB /* uribeacon */ = { - isa = PBXGroup; - children = ( - BD5418881BF15FA300F175DB /* NSString+UB.h */, - BD5418891BF15FA300F175DB /* NSString+UB.m */, - BD54188A1BF15FA300F175DB /* NSURL+UB.h */, - BD54188B1BF15FA300F175DB /* NSURL+UB.m */, - BD54188C1BF15FA300F175DB /* UBConfigurableUriBeacon.h */, - BD54188D1BF15FA300F175DB /* UBConfigurableUriBeacon.m */, - BD54188E1BF15FA300F175DB /* UBConfigurableURIBeaconPrivate.h */, - BD54188F1BF15FA300F175DB /* UBUriBeacon.h */, - BD5418901BF15FA300F175DB /* UBUriBeacon.m */, - BD5418911BF15FA300F175DB /* UBUriBeaconPrivate.h */, - BD5418921BF15FA300F175DB /* UBUriBeaconReader.h */, - BD5418931BF15FA300F175DB /* UBUriBeaconReader.m */, - BD5418941BF15FA300F175DB /* UBUriBeaconScanner.h */, - BD5418951BF15FA300F175DB /* UBUriBeaconScanner.m */, - BD5418961BF15FA300F175DB /* UBUriBeaconScannerPrivate.h */, - BD5418971BF15FA300F175DB /* UBUriBeaconWriter.h */, - BD5418981BF15FA300F175DB /* UBUriBeaconWriter.m */, - BD5418991BF15FA300F175DB /* UBUriReader.h */, - BD54189A1BF15FA300F175DB /* UBUriReader.m */, - BD54189B1BF15FA300F175DB /* UBUriWriter.h */, - BD54189C1BF15FA300F175DB /* UBUriWriter.m */, - BD54189D1BF15FA300F175DB /* UriBeacon.h */, - ); - path = uribeacon; - sourceTree = ""; - }; - BDA3FA8719EF2DD700CFAA74 /* Frameworks */ = { - isa = PBXGroup; - children = ( - BDA3FA8819EF2DD700CFAA74 /* NotificationCenter.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - BDA3FA8A19EF2DD700CFAA74 /* today */ = { - isa = PBXGroup; - children = ( - BDDFE6EE1A0069F000A76255 /* today.entitlements */, - BDA3FA8D19EF2DD700CFAA74 /* TodayViewController.h */, - BDA3FA8E19EF2DD700CFAA74 /* TodayViewController.m */, - BDA3FA8B19EF2DD700CFAA74 /* Supporting Files */, - ); - path = today; - sourceTree = ""; - }; - BDA3FA8B19EF2DD700CFAA74 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - BDA3FA8C19EF2DD700CFAA74 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - BDBFD66D1A2D125700A0B5E0 /* SVPullToRefresh */ = { - isa = PBXGroup; - children = ( - BDBFD66E1A2D125700A0B5E0 /* SVPullToRefresh.h */, - BDBFD66F1A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.h */, - BDBFD6701A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.m */, - BDBFD6711A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.h */, - BDBFD6721A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.m */, - ); - name = SVPullToRefresh; - path = "third-party/SVPullToRefresh/SVPullToRefresh"; - sourceTree = ""; - }; - BDC5A9B81B3B3D60008D9A0B /* JBChartView */ = { - isa = PBXGroup; - children = ( - BDDC71041B4B20C20000581C /* JBChartView.h */, - BDDC71051B4B20C20000581C /* JBChartView.m */, - BDDC71061B4B20C20000581C /* JBLineChartView.h */, - BDDC71071B4B20C20000581C /* JBLineChartView.m */, - ); - name = JBChartView; - sourceTree = ""; - }; - BDE9F17F19A7CDF100AA8DFB = { - isa = PBXGroup; - children = ( - BDE9F27E19ABEF3D00AA8DFB /* frameworks */, - BDE9F18A19A7CDF100AA8DFB /* PhyWeb */, - BDA3FA8A19EF2DD700CFAA74 /* today */, - BDA3FA8719EF2DD700CFAA74 /* Frameworks */, - BDE9F18919A7CDF100AA8DFB /* Products */, - ); - sourceTree = ""; - }; - BDE9F18919A7CDF100AA8DFB /* Products */ = { - isa = PBXGroup; - children = ( - BDE9F18819A7CDF100AA8DFB /* PhyWeb.app */, - BDA3FA8619EF2DD700CFAA74 /* today.appex */, - ); - name = Products; - sourceTree = ""; - }; - BDE9F18A19A7CDF100AA8DFB /* PhyWeb */ = { - isa = PBXGroup; - children = ( - BDDFE6ED1A0069B700A76255 /* PhyWeb.entitlements */, - BDE9F1CD19A7E68400AA8DFB /* third-party */, - BDE9F1AF19A7E46D00AA8DFB /* Backend */, - BDE9F1B219A7E46D00AA8DFB /* Resources */, - BDE9F1B619A7E46D00AA8DFB /* UI */, - BDE9F18F19A7CDF100AA8DFB /* AppDelegate.h */, - BDE9F19019A7CDF100AA8DFB /* AppDelegate.m */, - BDE9F19819A7CDF100AA8DFB /* Images.xcassets */, - BDE9F18B19A7CDF100AA8DFB /* Supporting Files */, - ); - path = PhyWeb; - sourceTree = ""; - }; - BDE9F18B19A7CDF100AA8DFB /* Supporting Files */ = { - isa = PBXGroup; - children = ( - BDE9F18C19A7CDF100AA8DFB /* Info.plist */, - BDE9F18D19A7CDF100AA8DFB /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - BDE9F1AF19A7E46D00AA8DFB /* Backend */ = { - isa = PBXGroup; - children = ( - BD5418871BF15FA300F175DB /* uribeacon */, - BDE9F1B019A7E46D00AA8DFB /* PWMetadataRequest.h */, - BDE9F1B119A7E46D00AA8DFB /* PWMetadataRequest.m */, - BDE9F1D819A7EAFC00AA8DFB /* PWBeaconManager.h */, - BDE9F1D919A7EAFC00AA8DFB /* PWBeaconManager.m */, - BDE9F1DE19A7EF3F00AA8DFB /* PWBeacon.h */, - BDE9F1DF19A7EF3F00AA8DFB /* PWBeacon.m */, - BDE9F1E119A7FB4000AA8DFB /* PWURLShortener.h */, - BDE9F1E219A7FB4000AA8DFB /* PWURLShortener.m */, - ); - path = Backend; - sourceTree = ""; - }; - BDE9F1B219A7E46D00AA8DFB /* Resources */ = { - isa = PBXGroup; - children = ( - BDE4D8C719E355B4007938F0 /* ScanError.png */, - BDE4D8B519DF65A4007938F0 /* Progress1.png */, - BDE4D8B619DF65A4007938F0 /* Progress2.png */, - BDE4D8B719DF65A4007938F0 /* Progress3.png */, - BDE4D8A719DE03F1007938F0 /* LaunchIcon.png */, - BDE4D88519DDF678007938F0 /* icon-29.png */, - BDE4D88619DDF678007938F0 /* icon-40.png */, - BDE4D88719DDF678007938F0 /* icon-50.png */, - BDE4D88819DDF678007938F0 /* icon-57.png */, - BDE4D88919DDF678007938F0 /* icon-58.png */, - BDE4D88A19DDF678007938F0 /* icon-72.png */, - BDE4D88B19DDF678007938F0 /* icon-76.png */, - BDE4D88C19DDF678007938F0 /* icon-80.png */, - BDE4D88D19DDF678007938F0 /* icon-87.png */, - BDE4D88E19DDF678007938F0 /* icon-100.png */, - BDE4D88F19DDF678007938F0 /* icon-114.png */, - BDE4D89019DDF678007938F0 /* icon-120.png */, - BDE4D89119DDF678007938F0 /* icon-144.png */, - BDE4D89219DDF678007938F0 /* icon-152.png */, - BDE4D89319DDF678007938F0 /* icon-180.png */, - BDE4D89419DDF678007938F0 /* icon-512.png */, - BDE9F2AC19AEA58200AA8DFB /* 37x-Checkmark.png */, - BDE9F2AD19AEA58200AA8DFB /* 37x-Checkmark@2x.png */, - BDE9F29819AD1D5000AA8DFB /* gear.png */, - BDE9F29919AD1D5000AA8DFB /* gear@2x.png */, - BDE4D8A519DE0242007938F0 /* LaunchScreen.xib */, - BDFC4DCF19E60C1A00DC381C /* licenses.html */, - ); - path = Resources; - sourceTree = ""; - }; - BDE9F1B619A7E46D00AA8DFB /* UI */ = { - isa = PBXGroup; - children = ( - BDE9F1B719A7E46D00AA8DFB /* PWBeaconCell.h */, - BDE9F1B819A7E46D00AA8DFB /* PWBeaconCell.m */, - BDE9F1B919A7E46D00AA8DFB /* PWBeaconsViewController.h */, - BDE9F1BA19A7E46D00AA8DFB /* PWBeaconsViewController.m */, - BDE9F1BB19A7E46D00AA8DFB /* PWGradientView.h */, - BDE9F1BC19A7E46D00AA8DFB /* PWGradientView.m */, - BDE9F1C119A7E46D00AA8DFB /* PWSignalStrengthView.h */, - BDE9F1C219A7E46D00AA8DFB /* PWSignalStrengthView.m */, - BDE9F29C19AD551500AA8DFB /* PWConfigureViewController.h */, - BDE9F29D19AD551500AA8DFB /* PWConfigureViewController.m */, - BDE9F29E19AD551500AA8DFB /* PWSettingsViewController.h */, - BDE9F29F19AD551500AA8DFB /* PWSettingsViewController.m */, - BDE4D8B219DE154B007938F0 /* PWPlaceholderView.h */, - BDE4D8B319DE154B007938F0 /* PWPlaceholderView.m */, - BDE4D8BB19DF65BE007938F0 /* PWActivityIndicator.h */, - BDE4D8BC19DF65BE007938F0 /* PWActivityIndicator.m */, - BDFC4DD619E60C5200DC381C /* PWSimpleWebViewController.h */, - BDFC4DD719E60C5200DC381C /* PWSimpleWebViewController.m */, - BDC5A9AA1B3B3A90008D9A0B /* PWChartViewController.h */, - BDC5A9AB1B3B3A90008D9A0B /* PWChartViewController.m */, - BD3E51B71B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.h */, - BD3E51B81B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.m */, - ); - path = UI; - sourceTree = ""; - }; - BDE9F1CD19A7E68400AA8DFB /* third-party */ = { - isa = PBXGroup; - children = ( - BDC5A9B81B3B3D60008D9A0B /* JBChartView */, - BDBFD66D1A2D125700A0B5E0 /* SVPullToRefresh */, - BD039BFA19D0E8BB005A7F37 /* MBProgressHUD.xcodeproj */, - BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */, - ); - name = "third-party"; - sourceTree = ""; - }; - BDE9F26F19ABE6DD00AA8DFB /* Products */ = { - isa = PBXGroup; - children = ( - BDE9F27619ABE6DE00AA8DFB /* libSDWebImage.a */, - BDE9F27819ABE6DE00AA8DFB /* libSDWebImage+WebP.a */, - BDE9F27A19ABE6DE00AA8DFB /* libSDWebImage+MKAnnotation.a */, - BD5418861BF15F7200F175DB /* WebImage.framework */, - ); - name = Products; - sourceTree = ""; - }; - BDE9F27E19ABEF3D00AA8DFB /* frameworks */ = { - isa = PBXGroup; - children = ( - BDE9F28519ABF02700AA8DFB /* ImageIO.framework */, - BDE9F28319ABEFB400AA8DFB /* CoreGraphics.framework */, - BDE9F28119ABEF5300AA8DFB /* CoreImage.framework */, - BDE9F27F19ABEF4D00AA8DFB /* CoreBluetooth.framework */, - ); - name = frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXLegacyTarget section */ - BDA3FA4F19E8A18300CFAA74 /* archive-build */ = { - isa = PBXLegacyTarget; - buildArgumentsString = "$(SRCROOT)/scripts/update-build.sh"; - buildConfigurationList = BDA3FA5019E8A18300CFAA74 /* Build configuration list for PBXLegacyTarget "archive-build" */; - buildPhases = ( - ); - buildToolPath = /bin/sh; - buildWorkingDirectory = "$(SRCROOT)/PhyWeb"; - dependencies = ( - ); - name = "archive-build"; - passBuildSettingsInEnvironment = 1; - productName = "archive-build"; - }; - BDA3FA5519EC8F0700CFAA74 /* make-dev */ = { - isa = PBXLegacyTarget; - buildArgumentsString = "$(SRCROOT)/scripts/make-dev.sh"; - buildConfigurationList = BDA3FA5619EC8F0700CFAA74 /* Build configuration list for PBXLegacyTarget "make-dev" */; - buildPhases = ( - ); - buildToolPath = /bin/sh; - buildWorkingDirectory = "$(SRCROOT)/PhyWeb"; - dependencies = ( - ); - name = "make-dev"; - passBuildSettingsInEnvironment = 1; - productName = "make dev"; - }; -/* End PBXLegacyTarget section */ - -/* Begin PBXNativeTarget section */ - BDA3FA8519EF2DD700CFAA74 /* today */ = { - isa = PBXNativeTarget; - buildConfigurationList = BDA3FA9E19EF2DD700CFAA74 /* Build configuration list for PBXNativeTarget "today" */; - buildPhases = ( - BDA3FA8219EF2DD700CFAA74 /* Sources */, - BDA3FA8319EF2DD700CFAA74 /* Frameworks */, - BDA3FA8419EF2DD700CFAA74 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = today; - productName = today; - productReference = BDA3FA8619EF2DD700CFAA74 /* today.appex */; - productType = "com.apple.product-type.app-extension"; - }; - BDE9F18719A7CDF100AA8DFB /* PhyWeb */ = { - isa = PBXNativeTarget; - buildConfigurationList = BDE9F1A819A7CDF100AA8DFB /* Build configuration list for PBXNativeTarget "PhyWeb" */; - buildPhases = ( - BDE9F18419A7CDF100AA8DFB /* Sources */, - BDE9F18519A7CDF100AA8DFB /* Frameworks */, - BDE9F18619A7CDF100AA8DFB /* Resources */, - BDA3FA9F19EF2DD700CFAA74 /* Embed App Extensions */, - ); - buildRules = ( - ); - dependencies = ( - BD039C0319D0E8DE005A7F37 /* PBXTargetDependency */, - BDE9F27C19ABE6EC00AA8DFB /* PBXTargetDependency */, - BDA3FA9319EF2DD700CFAA74 /* PBXTargetDependency */, - BDA3FA9619EF2DD700CFAA74 /* PBXTargetDependency */, - ); - name = PhyWeb; - productName = PhyWeb; - productReference = BDE9F18819A7CDF100AA8DFB /* PhyWeb.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BDE9F18019A7CDF100AA8DFB /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0600; - ORGANIZATIONNAME = "Hoa Dinh"; - TargetAttributes = { - BDA3FA4F19E8A18300CFAA74 = { - CreatedOnToolsVersion = 6.0.1; - }; - BDA3FA5519EC8F0700CFAA74 = { - CreatedOnToolsVersion = 6.0.1; - }; - BDA3FA8519EF2DD700CFAA74 = { - CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = A2PMJ2T6DQ; - SystemCapabilities = { - com.apple.ApplicationGroups.iOS = { - enabled = 1; - }; - }; - }; - BDE9F18719A7CDF100AA8DFB = { - CreatedOnToolsVersion = 6.0; - DevelopmentTeam = A2PMJ2T6DQ; - SystemCapabilities = { - com.apple.ApplicationGroups.iOS = { - enabled = 1; - }; - }; - }; - }; - }; - buildConfigurationList = BDE9F18319A7CDF100AA8DFB /* Build configuration list for PBXProject "PhyWeb" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = BDE9F17F19A7CDF100AA8DFB; - productRefGroup = BDE9F18919A7CDF100AA8DFB /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = BD039BFB19D0E8BB005A7F37 /* Products */; - ProjectRef = BD039BFA19D0E8BB005A7F37 /* MBProgressHUD.xcodeproj */; - }, - { - ProductGroup = BDE9F26F19ABE6DD00AA8DFB /* Products */; - ProjectRef = BDE9F26E19ABE6DD00AA8DFB /* SDWebImage.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - BDE9F18719A7CDF100AA8DFB /* PhyWeb */, - BDA3FA4F19E8A18300CFAA74 /* archive-build */, - BDA3FA5519EC8F0700CFAA74 /* make-dev */, - BDA3FA8519EF2DD700CFAA74 /* today */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - BD039BFF19D0E8BB005A7F37 /* libMBProgressHUD.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libMBProgressHUD.a; - remoteRef = BD039BFE19D0E8BB005A7F37 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BD5418861BF15F7200F175DB /* WebImage.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = WebImage.framework; - remoteRef = BD5418851BF15F7200F175DB /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BDE9F27619ABE6DE00AA8DFB /* libSDWebImage.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libSDWebImage.a; - remoteRef = BDE9F27519ABE6DE00AA8DFB /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BDE9F27819ABE6DE00AA8DFB /* libSDWebImage+WebP.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libSDWebImage+WebP.a"; - remoteRef = BDE9F27719ABE6DE00AA8DFB /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - BDE9F27A19ABE6DE00AA8DFB /* libSDWebImage+MKAnnotation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libSDWebImage+MKAnnotation.a"; - remoteRef = BDE9F27919ABE6DE00AA8DFB /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - BDA3FA8419EF2DD700CFAA74 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDE9F18619A7CDF100AA8DFB /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BDE4D8B919DF65A4007938F0 /* Progress2.png in Resources */, - BDE9F2AF19AEA58200AA8DFB /* 37x-Checkmark@2x.png in Resources */, - BDE4D8BA19DF65A4007938F0 /* Progress3.png in Resources */, - BDE9F29A19AD1D5000AA8DFB /* gear.png in Resources */, - BDE4D8A619DE0242007938F0 /* LaunchScreen.xib in Resources */, - BDE9F2AE19AEA58200AA8DFB /* 37x-Checkmark.png in Resources */, - BDFC4DD019E60C1A00DC381C /* licenses.html in Resources */, - BDE9F29B19AD1D5000AA8DFB /* gear@2x.png in Resources */, - BDE9F19919A7CDF100AA8DFB /* Images.xcassets in Resources */, - BDE4D8B819DF65A4007938F0 /* Progress1.png in Resources */, - BDE4D8A819DE03F1007938F0 /* LaunchIcon.png in Resources */, - BDE4D8C819E355B4007938F0 /* ScanError.png in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - BDA3FA8219EF2DD700CFAA74 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BD5418A91BF15FCF00F175DB /* UBConfigurableUriBeacon.m in Sources */, - BDA3FAA719EF338F00CFAA74 /* PWBeaconCell.m in Sources */, - BD5418A81BF15FCF00F175DB /* NSURL+UB.m in Sources */, - BD5418AA1BF15FCF00F175DB /* UBUriBeacon.m in Sources */, - BDA3FAA819EF38A600CFAA74 /* PWSignalStrengthView.m in Sources */, - BD5418A71BF15FCF00F175DB /* NSString+UB.m in Sources */, - BD5418AE1BF15FCF00F175DB /* UBUriReader.m in Sources */, - BD5418AD1BF15FCF00F175DB /* UBUriBeaconWriter.m in Sources */, - BDA3FAA519EF329900CFAA74 /* PWBeacon.m in Sources */, - BDA3FAA319EF329400CFAA74 /* PWBeaconManager.m in Sources */, - BD5418AC1BF15FCF00F175DB /* UBUriBeaconScanner.m in Sources */, - BD5418AB1BF15FCF00F175DB /* UBUriBeaconReader.m in Sources */, - BD5418B01BF1613D00F175DB /* UBUriWriter.m in Sources */, - BDA3FAA419EF329700CFAA74 /* PWMetadataRequest.m in Sources */, - BDA3FA8F19EF2DD700CFAA74 /* TodayViewController.m in Sources */, - BDA3FAA619EF329C00CFAA74 /* PWURLShortener.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BDE9F18419A7CDF100AA8DFB /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - BDE4D8B419DE154B007938F0 /* PWPlaceholderView.m in Sources */, - BDE9F1E019A7EF3F00AA8DFB /* PWBeacon.m in Sources */, - BDE9F1C719A7E46D00AA8DFB /* PWBeaconCell.m in Sources */, - BDE9F19119A7CDF100AA8DFB /* AppDelegate.m in Sources */, - BDFC4DD819E60C5200DC381C /* PWSimpleWebViewController.m in Sources */, - BD5418A31BF15FA300F175DB /* UBUriBeaconScanner.m in Sources */, - BD5418A41BF15FA300F175DB /* UBUriBeaconWriter.m in Sources */, - BDDC71091B4B20C20000581C /* JBLineChartView.m in Sources */, - BD5418A21BF15FA300F175DB /* UBUriBeaconReader.m in Sources */, - BDE9F1C819A7E46D00AA8DFB /* PWBeaconsViewController.m in Sources */, - BDE9F2A019AD551500AA8DFB /* PWConfigureViewController.m in Sources */, - BD54189E1BF15FA300F175DB /* NSString+UB.m in Sources */, - BDE9F1C919A7E46D00AA8DFB /* PWGradientView.m in Sources */, - BD5418A51BF15FA300F175DB /* UBUriReader.m in Sources */, - BDC5A9AC1B3B3A90008D9A0B /* PWChartViewController.m in Sources */, - BDE9F1E319A7FB4000AA8DFB /* PWURLShortener.m in Sources */, - BDE9F1C319A7E46D00AA8DFB /* PWMetadataRequest.m in Sources */, - BD54189F1BF15FA300F175DB /* NSURL+UB.m in Sources */, - BDE9F2A119AD551500AA8DFB /* PWSettingsViewController.m in Sources */, - BD3E51B91B4C4ED000F3ECBF /* PWBeaconChartTableViewCell.m in Sources */, - BDE9F18E19A7CDF100AA8DFB /* main.m in Sources */, - BDBFD6731A2D125700A0B5E0 /* UIScrollView+SVInfiniteScrolling.m in Sources */, - BD5418A61BF15FA300F175DB /* UBUriWriter.m in Sources */, - BDDC71081B4B20C20000581C /* JBChartView.m in Sources */, - BDE9F1DA19A7EAFC00AA8DFB /* PWBeaconManager.m in Sources */, - BD5418A01BF15FA300F175DB /* UBConfigurableUriBeacon.m in Sources */, - BDBFD6741A2D125700A0B5E0 /* UIScrollView+SVPullToRefresh.m in Sources */, - BDE9F1CC19A7E46D00AA8DFB /* PWSignalStrengthView.m in Sources */, - BD5418A11BF15FA300F175DB /* UBUriBeacon.m in Sources */, - BDE4D8BD19DF65BE007938F0 /* PWActivityIndicator.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - BD039C0319D0E8DE005A7F37 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = MBProgressHUD; - targetProxy = BD039C0219D0E8DE005A7F37 /* PBXContainerItemProxy */; - }; - BDA3FA9319EF2DD700CFAA74 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BDA3FA8519EF2DD700CFAA74 /* today */; - targetProxy = BDA3FA9219EF2DD700CFAA74 /* PBXContainerItemProxy */; - }; - BDA3FA9619EF2DD700CFAA74 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BDA3FA8519EF2DD700CFAA74 /* today */; - targetProxy = BDA3FA9519EF2DD700CFAA74 /* PBXContainerItemProxy */; - }; - BDE9F27C19ABE6EC00AA8DFB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SDWebImage; - targetProxy = BDE9F27B19ABE6EC00AA8DFB /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - BDA3FA5119E8A18300CFAA74 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEBUGGING_SYMBOLS = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - MACOSX_DEPLOYMENT_TARGET = 10.9; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Debug; - }; - BDA3FA5219E8A18300CFAA74 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - MACOSX_DEPLOYMENT_TARGET = 10.9; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Release; - }; - BDA3FA5719EC8F0700CFAA74 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEBUGGING_SYMBOLS = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - MACOSX_DEPLOYMENT_TARGET = 10.9; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Debug; - }; - BDA3FA5819EC8F0700CFAA74 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - MACOSX_DEPLOYMENT_TARGET = 10.9; - OTHER_CFLAGS = ""; - OTHER_LDFLAGS = ""; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Release; - }; - BDA3FA9719EF2DD700CFAA74 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = today/today.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "TODAY_EXTENSION=1", - ); - INFOPLIST_FILE = today/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - BDA3FA9819EF2DD700CFAA74 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = today/today.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "TODAY_EXTENSION=1", - ); - INFOPLIST_FILE = today/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - SKIP_INSTALL = YES; - }; - name = Release; - }; - BDE9F1A619A7CDF100AA8DFB /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - BDE9F1A719A7CDF100AA8DFB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - BDE9F1A919A7CDF100AA8DFB /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = PhyWeb/PhyWeb.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - INFOPLIST_FILE = PhyWeb/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/PhyWeb-cbgefceavrhccbcxfekuupylonps/Build/Products/Debug-iphoneos", - ); - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - }; - name = Debug; - }; - BDE9F1AA19A7CDF100AA8DFB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = PhyWeb/PhyWeb.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - INFOPLIST_FILE = PhyWeb/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/PhyWeb-cbgefceavrhccbcxfekuupylonps/Build/Products/Debug-iphoneos", - ); - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - BDA3FA5019E8A18300CFAA74 /* Build configuration list for PBXLegacyTarget "archive-build" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDA3FA5119E8A18300CFAA74 /* Debug */, - BDA3FA5219E8A18300CFAA74 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BDA3FA5619EC8F0700CFAA74 /* Build configuration list for PBXLegacyTarget "make-dev" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDA3FA5719EC8F0700CFAA74 /* Debug */, - BDA3FA5819EC8F0700CFAA74 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BDA3FA9E19EF2DD700CFAA74 /* Build configuration list for PBXNativeTarget "today" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDA3FA9719EF2DD700CFAA74 /* Debug */, - BDA3FA9819EF2DD700CFAA74 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BDE9F18319A7CDF100AA8DFB /* Build configuration list for PBXProject "PhyWeb" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDE9F1A619A7CDF100AA8DFB /* Debug */, - BDE9F1A719A7CDF100AA8DFB /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BDE9F1A819A7CDF100AA8DFB /* Build configuration list for PBXNativeTarget "PhyWeb" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BDE9F1A919A7CDF100AA8DFB /* Debug */, - BDE9F1AA19A7CDF100AA8DFB /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = BDE9F18019A7CDF100AA8DFB /* Project object */; -} diff --git a/ios/PhyWeb/AppDelegate.h b/ios/PhyWeb/AppDelegate.h deleted file mode 100644 index d4210e62..00000000 --- a/ios/PhyWeb/AppDelegate.h +++ /dev/null @@ -1,23 +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. - */ - -#import - -@interface AppDelegate : UIResponder - -@property(strong, nonatomic) UIWindow *window; - -@end diff --git a/ios/PhyWeb/AppDelegate.m b/ios/PhyWeb/AppDelegate.m deleted file mode 100644 index 1637891b..00000000 --- a/ios/PhyWeb/AppDelegate.m +++ /dev/null @@ -1,52 +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. - */ - -#import "AppDelegate.h" - -#import "PWBeaconsViewController.h" -#import "PWBeaconManager.h" - -@interface AppDelegate () - -@end - -@implementation AppDelegate { - PWBeaconsViewController *_mainViewController; -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - if ([[PWBeaconManager sharedManager] isStarted]) { - [[PWBeaconManager sharedManager] stop]; - } - [_mainViewController disablePlaceholder]; - [[PWBeaconManager sharedManager] resetBeacons]; - [[PWBeaconManager sharedManager] start]; - [_mainViewController updateBeaconsNow]; -} - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.backgroundColor = [UIColor whiteColor]; - [self.window makeKeyAndVisible]; - - _mainViewController = [[PWBeaconsViewController alloc] init]; - [[self window] setRootViewController:_mainViewController]; - - return YES; -} - -@end diff --git a/ios/PhyWeb/Backend/PWBeacon.h b/ios/PhyWeb/Backend/PWBeacon.h deleted file mode 100644 index af8a476d..00000000 --- a/ios/PhyWeb/Backend/PWBeacon.h +++ /dev/null @@ -1,74 +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. - */ - -#import -#import "UriBeacon.h" - -@interface PWBeacon : NSObject - -- (id)initWithUriBeacon:(UBUriBeacon *)beacon info:(NSDictionary *)info; - -@property(nonatomic, retain) UBUriBeacon *uriBeacon; - -// URL of the page stored on the beacon. -@property(nonatomic, copy) NSURL *URL; - -// URL of the page to display to the user. -@property(nonatomic, copy) NSURL *displayURL; - -// Whether the display URL has been set by the server. -@property(nonatomic, assign) BOOL hasDisplayURL; - -// Title of the page. -@property(nonatomic, copy) NSString *title; - -// Snippet of the page. -@property(nonatomic, copy) NSString *snippet; - -// Favicon. -@property(nonatomic, copy) NSURL *iconURL; - -// Date of discovery of the beacon. -@property(nonatomic, retain) NSDate *date; - -// The beacon is in the first batch. We should sort it by region. -@property(nonatomic, assign) BOOL sortByRegion; - -// Region of the beacon. -@property(nonatomic, assign) UBUriBeaconRegion region; - -// Rank of the beacon, computed by the metadata server. -@property(nonatomic, assign) double rank; - -// Returns YES if the rank has been computed by the metadata server. -@property(nonatomic, assign) BOOL hasRank; - -// Returns the delay to discover the beacon via bluetooth. -@property(nonatomic, assign) NSTimeInterval discoveryDelay; - -// Returns the delay to get the metadata of the beacon via The Physical Web -// Server. -@property(nonatomic, assign) NSTimeInterval requestDelay; - -@property (nonatomic, retain, readonly) NSArray * rssiHistory; - -// Returns the region name of the beacon when it was created. -- (NSString *)debugRegionName; - -// Returns the updated region name of the beacon. -- (NSString *)debugUriRegionName; - -@end diff --git a/ios/PhyWeb/Backend/PWBeacon.m b/ios/PhyWeb/Backend/PWBeacon.m deleted file mode 100644 index 06810e34..00000000 --- a/ios/PhyWeb/Backend/PWBeacon.m +++ /dev/null @@ -1,117 +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. -*/ - -#import "PWBeacon.h" - -@implementation PWBeacon { - NSMutableArray * _rssiHistory; -} - -- (id)initWithUriBeacon:(UBUriBeacon *)beacon info:(NSDictionary *)info { - self = [self init]; - - _rssiHistory = [[NSMutableArray alloc] init]; - NSString *title = info[@"title"]; - if (title == (NSString *)[NSNull null]) { - title = nil; - } - NSString *desc = info[@"description"]; - if (desc == (NSString *)[NSNull null]) { - desc = nil; - } - NSString *icon = info[@"icon"]; - if (icon == (NSString *)[NSNull null]) { - icon = nil; - } - NSString *urlString = info[@"url"]; - if (urlString == (NSString *)[NSNull null]) { - urlString = nil; - } - NSString *displayUrlString = info[@"displayUrl"]; - if (displayUrlString == (NSString *)[NSNull null]) { - displayUrlString = nil; - } - NSNumber *rankNumber = info[@"rank"]; - if (rankNumber == (NSNumber *)[NSNull null]) { - rankNumber = nil; - } - - if ([title length] == 0) { - title = nil; - } - if ([desc length] == 0) { - desc = nil; - } - if ([icon length] == 0) { - icon = nil; - } - - [self setUriBeacon:beacon]; - if (beacon != nil) { - [self setURL:[beacon URI]]; - } else if (urlString != nil) { - [self setURL:[NSURL URLWithString:urlString]]; - } - [self setHasDisplayURL:displayUrlString != nil]; - [self setDisplayURL:displayUrlString != nil - ? [NSURL URLWithString:displayUrlString] - : [self URL]]; - [self setTitle:title]; - [self setSnippet:desc]; - [self setIconURL:[NSURL URLWithString:icon]]; - if (rankNumber != nil) { - [self setHasRank:YES]; - [self setRank:[rankNumber doubleValue]]; - } - - return self; -} - -static NSString *regionName(UBUriBeaconRegion region) { - switch (region) { - case UBUriBeaconRegionNear: - return @"near"; - case UBUriBeaconRegionMid: - return @"mid"; - case UBUriBeaconRegionFar: - return @"far"; - case UBUriBeaconRegionUnknown: - default: - return @"unk"; - } -} - -- (NSString *)debugRegionName { - return regionName([self region]); -} - -- (NSString *)debugUriRegionName { - return regionName([[self uriBeacon] region]); -} - -- (NSArray *) rssiHistory { - return _rssiHistory; -} - -- (void) setUriBeacon:(UBUriBeacon *)uriBeacon { - NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate]; - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DebugMode"]) { - [_rssiHistory addObject:@[[NSNumber numberWithInt:(int) [uriBeacon RSSI]], [NSNumber numberWithDouble:timestamp]]]; - } - _uriBeacon = uriBeacon; -} - -@end diff --git a/ios/PhyWeb/Backend/PWBeaconManager.h b/ios/PhyWeb/Backend/PWBeaconManager.h deleted file mode 100644 index bddf40b6..00000000 --- a/ios/PhyWeb/Backend/PWBeaconManager.h +++ /dev/null @@ -1,72 +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. - */ - -#import -#import "UriBeacon.h" - -@interface PWBeaconManager : NSObject - -// Singleton. -+ (instancetype)sharedManager; - -// Returns the list of nearby augmented beacons. -- (NSArray* /* PWBeacon */)beacons; - -// Returns the list of configurable beacons. -- (NSArray* /* UBConfigurableUriBeacon */)configurableBeacons; - -- (NSTimeInterval) startTime; - -// Register a callback block when there's a change in the list of beacons. -// It returns an object that need to be used to unregister the callback. -// It needs to be passed to -unregisterChangeBlock:. -- (id)registerChangeBlock:(void (^)(void))block; - -// Unregister a handler of change in list of beacons. -- (void)unregisterChangeBlock:(id)registeredBlock; - -// Registers a callback block when the list of configurable beacons changed. -// It returns an object that need to be used to unregister the callback. -// It needs to be passed to -unregisterConfigurationChangeBlock:. -- (id)registerConfigurationChangeBlock:(void (^)(void))block; - -// Unregister a handler of change in list of configurable beacons. -- (void)unregisterConfigurationChangeBlock:(id)registeredBlock; - -// Starts scanning for beacons. -- (void)start; - -// Stops scanning for beacons. -- (void)stop; - -// Empty the list of beacons. -- (void)resetBeacons; - -// Returns YES is it's scanning for beacons. -- (BOOL)isStarted; - -// Store beacons in defaults shared between the today extension and the app. -// It will save only the URL and the title since it's only what we need to -// show them in the today extension. -- (void)serializeBeacons:(NSArray*)beacons; - -// Retrieve the beacons from the defaults. -- (NSArray*)unserializedBeacons; - -// List won't reorder if it's set to YES. -@property(nonatomic, assign, getter=isStableMode) BOOL stableMode; - -@end diff --git a/ios/PhyWeb/Backend/PWBeaconManager.m b/ios/PhyWeb/Backend/PWBeaconManager.m deleted file mode 100644 index b860562b..00000000 --- a/ios/PhyWeb/Backend/PWBeaconManager.m +++ /dev/null @@ -1,433 +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. - */ - -#import "PWBeaconManager.h" - -#import "UriBeacon.h" - -#import "PWBeacon.h" -#import "PWMetadataRequest.h" - -#define DISCOVERY_DELAY_KEY @"discoveryDelay" - -@interface PWBeaconManager () - -@end - -@implementation PWBeaconManager { - // Underlying beacon scanner from UriBeacon library. - UBUriBeaconScanner* _scanner; - // Map of URL to augmented beacons. - NSMutableDictionary* _beaconsDict; /* URL -> PWBeacon */ - // Handler when the list of beacons change. - NSMutableArray* /* Block */ _changeBlocks; - // Handler when the list of configurable beacons change. - NSMutableArray* /* Block */ _configurationChangeBlocks; - // List of metadata requests. - NSMutableArray* /* PWMetadataRequest */ _requests; - // Set of URLs that are being requested. - NSMutableSet* /* NSURL */ _pendingURLRequest; - // Whether the beacon manager started. - BOOL _started; - // mDNS browser for http. - NSNetServiceBrowser* _httpServiceBrowser; - // mDNS browser for https. - NSNetServiceBrowser* _httpsServiceBrowser; - // mDNS found that we need to resolve. - NSMutableArray* _pendingNetServices; - // Whether a resolution is in progress. - BOOL _resolving; - // URL related to a given service. - NSMutableDictionary* _discoveredNetServicesURLs; - // Names of the discovered services. - NSMutableSet* _netServicesNames; - NSTimeInterval _startTime; - NSMutableArray* _pendingBeaconsInfos; -} - -- (id)init { - self = [super init]; - if (!self) { - return nil; - } - _beaconsDict = [NSMutableDictionary dictionary]; - _changeBlocks = [NSMutableArray array]; - _configurationChangeBlocks = [NSMutableArray array]; - _requests = [NSMutableArray array]; - _pendingURLRequest = [NSMutableSet set]; - _pendingNetServices = [[NSMutableArray alloc] init]; - _pendingBeaconsInfos = [[NSMutableArray alloc] init]; - _stableMode = YES; - return self; -} - -+ (instancetype)sharedManager { - static dispatch_once_t onceToken; - static PWBeaconManager* instance = nil; - dispatch_once(&onceToken, ^{ - if (instance == nil) { - instance = [[PWBeaconManager alloc] init]; - } - }); - return instance; -} - -- (NSArray* /* PWBeacon */)beacons { - return [_beaconsDict allValues]; -} - -- (NSArray* /* UBConfigurableUriBeacon */)configurableBeacons { - return [_scanner configurableBeacons]; -} - -- (id)registerChangeBlock:(void (^)(void))block { - void (^blockCopy)(void) = [block copy]; - [_changeBlocks addObject:blockCopy]; - return blockCopy; -} - -- (void)unregisterChangeBlock:(id)registeredBlock { - [_changeBlocks removeObject:registeredBlock]; -} - -- (id)registerConfigurationChangeBlock:(void (^)(void))block { - void (^blockCopy)(void) = [block copy]; - [_configurationChangeBlocks addObject:blockCopy]; - return blockCopy; -} - -- (void)unregisterConfigurationChangeBlock:(id)registeredBlock { - [_configurationChangeBlocks removeObject:registeredBlock]; -} - -- (void)start { - _startTime = [NSDate timeIntervalSinceReferenceDate]; - _started = YES; - PWBeaconManager* __weak weakSelf = self; -#if TODAY_EXTENSION - _scanner = [[UBUriBeaconScanner alloc] initWithApplication:nil]; -#else - _scanner = [[UBUriBeaconScanner alloc] - initWithApplication:[UIApplication sharedApplication]]; -#endif - [_scanner startScanningWithUpdateBlock:^{ - PWBeaconManager* strongSelf = weakSelf; - [strongSelf _updateBeacons]; - }]; - _httpServiceBrowser = [[NSNetServiceBrowser alloc] init]; - [_httpServiceBrowser setDelegate:self]; - _httpsServiceBrowser = [[NSNetServiceBrowser alloc] init]; - [_httpsServiceBrowser setDelegate:self]; - [_httpServiceBrowser searchForServicesOfType:@"_http._tcp." inDomain:@""]; - [_httpsServiceBrowser searchForServicesOfType:@"_https._tcp." inDomain:@""]; - [_pendingNetServices removeAllObjects]; - _discoveredNetServicesURLs = [NSMutableDictionary dictionary]; - _netServicesNames = [NSMutableSet set]; - _resolving = NO; -} - -- (NSURL*)_urlWithNetService:(NSNetService*)service { - NSDictionary* txtRecords = - [NSNetService dictionaryFromTXTRecordData:[service TXTRecordData]]; - NSString* path = - [[NSString alloc] initWithData:[txtRecords objectForKey:@"path"] - encoding:NSUTF8StringEncoding]; - NSString* scheme = @"http"; - if ([[service type] isEqualToString:@"_http._tcp."]) { - scheme = @"http"; - } else if ([[service type] isEqualToString:@"_https._tcp."]) { - scheme = @"https"; - } - NSString* hostname = [service hostName]; - NSString* urlString = nil; - if ([hostname hasSuffix:@"."]) { - hostname = [hostname substringToIndex:[hostname length] - 1]; - } - if (([scheme isEqualToString:@"http"] && [service port] == 80) || - ([scheme isEqualToString:@"https"] && [service port] == 443)) { - urlString = [NSString stringWithFormat:@"%@://%@", scheme, hostname]; - } else { - urlString = [NSString - stringWithFormat:@"%@://%@:%i", scheme, hostname, (int)[service port]]; - } - if ([path length] != 0) { - if ([path hasPrefix:@"/"]) { - urlString = [urlString stringByAppendingString:path]; - } else { - urlString = [urlString stringByAppendingFormat:@"/%@", path]; - } - } - return [NSURL URLWithString:urlString]; -} - -- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser - didFindService:(NSNetService*)netService - moreComing:(BOOL)moreServicesComing { - [_pendingNetServices addObject:netService]; - NSString* name = [NSString - stringWithFormat:@"%@:%@", [netService type], [netService name]]; - [_netServicesNames addObject:name]; - - if (!moreServicesComing) { - [self _resolveNextNetService]; - } -} - -- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser - didRemoveService:(NSNetService*)netService - moreComing:(BOOL)moreServicesComing { - NSString* name = [NSString - stringWithFormat:@"%@:%@", [netService type], [netService name]]; - [_netServicesNames removeObject:name]; - [_discoveredNetServicesURLs removeObjectForKey:name]; - [self _cleanup]; -} - -- (void)_resolveNextNetService { - if (_resolving) { - return; - } - if ([_pendingNetServices count] == 0) { - return; - } - - _resolving = YES; - - NSNetService* netService = [_pendingNetServices objectAtIndex:0]; - [netService setDelegate:self]; - [netService resolveWithTimeout:0.5]; - - [self performSelector:@selector(_skipResolve) withObject:nil afterDelay:0.5]; -} - -- (void)_skipResolve { - _resolving = NO; - NSNetService* netService = [_pendingNetServices objectAtIndex:0]; - [netService stop]; - - [self _resolveNextNetService]; -} - -- (void)netServiceDidResolveAddress:(NSNetService*)netService { - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_skipResolve) - object:nil]; - _resolving = NO; - NSURL* url = [self _urlWithNetService:netService]; - NSString* name = [NSString - stringWithFormat:@"%@:%@", [netService type], [netService name]]; - [_discoveredNetServicesURLs setObject:url forKey:name]; - [netService stop]; - [_pendingNetServices removeObject:netService]; - [self _resolveNextNetService]; - - PWBeacon* beacon = [_beaconsDict objectForKey:url]; - if (beacon == nil) { - if (![_pendingURLRequest containsObject:url]) { - // Request metadata of a new beacon. - UBUriBeacon* uriBeacon = - [[UBUriBeacon alloc] initWithURI:url txPowerLevel:0]; - NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; - NSTimeInterval discoveryDelay = currentTime - _startTime; - NSMutableDictionary* pendingBeaconInfo = - [[NSMutableDictionary alloc] init]; - pendingBeaconInfo[DISCOVERY_DELAY_KEY] = - [NSNumber numberWithDouble:discoveryDelay]; - [_pendingBeaconsInfos addObject:pendingBeaconInfo]; - PWMetadataRequest* request = [[PWMetadataRequest alloc] init]; - [request setUriBeacons:@[ uriBeacon ]]; - [request setDelegate:self]; - [request start]; - [_pendingURLRequest addObject:[uriBeacon URI]]; - [_requests addObject:request]; - } - } -} - -- (void)netService:(NSNetService*)netService - didNotResolve:(NSDictionary*)errorDict { - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_skipResolve) - object:nil]; - _resolving = NO; - [netService stop]; - [_pendingNetServices removeObject:netService]; - [self _resolveNextNetService]; -} - -- (void)resetBeacons { - [_beaconsDict removeAllObjects]; - [self _notify]; -} - -- (void)_updateBeacons { - [self _notifyConfiguration]; - - BOOL hasChange = NO; - for (UBUriBeacon* uriBeacon in [_scanner beacons]) { - if ([uriBeacon URI] == nil) { - continue; - } - if ([uriBeacon RSSI] == 127) { - continue; - } - if (!([[[[uriBeacon URI] scheme] lowercaseString] - isEqualToString:@"http"] || - [[[[uriBeacon URI] scheme] lowercaseString] - isEqualToString:@"https"])) { - continue; - } - PWBeacon* beacon = [_beaconsDict objectForKey:[uriBeacon URI]]; - if (beacon == nil) { - if (![_pendingURLRequest containsObject:[uriBeacon URI]]) { - // Request metadata of a new beacon. - NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; - NSTimeInterval discoveryDelay = currentTime - _startTime; - NSMutableDictionary* pendingBeaconInfo = - [[NSMutableDictionary alloc] init]; - pendingBeaconInfo[DISCOVERY_DELAY_KEY] = - [NSNumber numberWithDouble:discoveryDelay]; - [_pendingBeaconsInfos addObject:pendingBeaconInfo]; - PWMetadataRequest* request = [[PWMetadataRequest alloc] init]; - [request setUriBeacons:@[ uriBeacon ]]; - [request setDelegate:self]; - [request start]; - [_pendingURLRequest addObject:[uriBeacon URI]]; - [_requests addObject:request]; - } - } else { - // Updated existing beacon. - [beacon setUriBeacon:uriBeacon]; - hasChange = YES; - } - } - if ([self _cleanup]) { - hasChange = YES; - } - if (hasChange) { - [self _notify]; - } -} - -- (void)metadataRequest_done:(PWMetadataRequest*)request { - UBUriBeacon* uriBeacon = [[request uriBeacons] objectAtIndex:0]; - PWBeacon* beacon = nil; - if ([request error] == nil && [[request results] count] > 0) { - beacon = [[request results] objectAtIndex:0]; - } else { - beacon = [[PWBeacon alloc] initWithUriBeacon:uriBeacon info:nil]; - } - - [_beaconsDict setObject:beacon forKey:[[beacon uriBeacon] URI]]; - NSUInteger idx = [_requests indexOfObject:request]; - if (idx != NSNotFound) { - NSDictionary* beaconInfo = [_pendingBeaconsInfos objectAtIndex:idx]; - NSTimeInterval discoveryDelay = - [beaconInfo[DISCOVERY_DELAY_KEY] doubleValue]; - NSTimeInterval requestDelay = [request delay]; - [beacon setDiscoveryDelay:discoveryDelay]; - [beacon setRequestDelay:requestDelay]; - [_pendingURLRequest removeObject:[uriBeacon URI]]; - [_pendingBeaconsInfos removeObjectAtIndex:idx]; - [_requests removeObjectAtIndex:idx]; - } - [self _notify]; -} - -// Remove expired beacons. -- (BOOL)_cleanup { - BOOL hasChange = NO; - if (![self isStableMode]) { - NSMutableSet* existingUrls = [NSMutableSet set]; - for (UBUriBeacon* beacon in [_scanner beacons]) { - if ([beacon URI] != nil) { - [existingUrls addObject:[beacon URI]]; - } - } - [existingUrls addObjectsFromArray:[_discoveredNetServicesURLs allValues]]; - for (NSURL* key in [_beaconsDict allKeys]) { - if (![existingUrls containsObject:key]) { - [_beaconsDict removeObjectForKey:key]; - hasChange = YES; - } - } - } - return hasChange; -} - -- (void)_notify { - for (void (^block)(void)in [_changeBlocks copy]) { - block(); - } -} - -- (void)_notifyConfiguration { - for (void (^block)(void)in [_configurationChangeBlocks copy]) { - block(); - } -} - -- (void)stop { - [_httpServiceBrowser stop]; - [_httpsServiceBrowser stop]; - [_scanner stopScanning]; - _started = NO; -} - -- (BOOL)isStarted { - return _started; -} - -- (void)serializeBeacons:(NSArray*)beacons { - NSUserDefaults* shared = - [[NSUserDefaults alloc] initWithSuiteName:@"group.physical-web.iosapp"]; - NSMutableArray* encoded = [[NSMutableArray alloc] init]; - for (PWBeacon* beacon in beacons) { - NSMutableDictionary* item = [[NSMutableDictionary alloc] init]; - if ([beacon URL] != nil) { - item[@"url"] = [[beacon URL] absoluteString]; - } - if ([beacon title] != nil) { - item[@"title"] = [beacon title]; - } - [encoded addObject:item]; - } - [shared setObject:encoded forKey:@"LastSeenBeacons"]; - [shared synchronize]; -} - -- (NSArray*)unserializedBeacons { - NSUserDefaults* shared = - [[NSUserDefaults alloc] initWithSuiteName:@"group.physical-web.iosapp"]; - NSArray* encoded = [shared objectForKey:@"LastSeenBeacons"]; - NSMutableArray* beacons = [[NSMutableArray alloc] init]; - for (NSDictionary* item in encoded) { - PWBeacon* beacon = [[PWBeacon alloc] init]; - if (item[@"url"] != nil) { - [beacon setURL:[NSURL URLWithString:item[@"url"]]]; - } - [beacon setTitle:item[@"title"]]; - [beacons addObject:beacon]; - } - return beacons; -} - -- (NSTimeInterval)startTime { - return _startTime; -} - -@end diff --git a/ios/PhyWeb/Backend/PWMetadataRequest.h b/ios/PhyWeb/Backend/PWMetadataRequest.h deleted file mode 100644 index b93030e0..00000000 --- a/ios/PhyWeb/Backend/PWMetadataRequest.h +++ /dev/null @@ -1,60 +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. - */ - -#import -#import "UriBeacon.h" - -@protocol PWMetadataRequestDelegate; - -// This class will request to the metadata server the information about a -// beacon. -@interface PWMetadataRequest : NSObject - -// Returns the name of the physical web server. -+ (NSString *)hostname; - -// The list of peripherals. -@property(nonatomic, retain) NSArray * /* UBUriBeacon */ uriBeacons; - -// Requests demo meta data. -@property(nonatomic, assign, getter=isDemo) BOOL demo; - -// Delegate of the request. The delegate will be notified when the request is -// done. -@property(nonatomic, weak) id delegate; - -// List of beacons augmented with their metadata. -@property(nonatomic, retain, readonly) NSArray * /* PWBeacon */ results; - -// It will be nil if no error happened. Otherwise, it's the error that happened -// during the request. -@property(nonatomic, retain, readonly) NSError *error; - -@property(nonatomic, assign, readonly) NSTimeInterval delay; - -// Start the request. -- (void)start; - -// Cancel the request. -- (void)cancel; - -@end - -@protocol PWMetadataRequestDelegate - -- (void)metadataRequest_done:(PWMetadataRequest *)request; - -@end diff --git a/ios/PhyWeb/Backend/PWMetadataRequest.m b/ios/PhyWeb/Backend/PWMetadataRequest.m deleted file mode 100644 index 378d02cb..00000000 --- a/ios/PhyWeb/Backend/PWMetadataRequest.m +++ /dev/null @@ -1,155 +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. - */ - -#import "PWMetadataRequest.h" - -#import -#import "PWBeacon.h" - -#define PHYSICALWEB_SERVER_HOSTNAME @"url-caster.appspot.com" -#define PHYSICALWEB_SERVER_HOSTNAME_DEV @"url-caster-dev.appspot.com" - -@interface PWMetadataRequest () - -@end - -@implementation PWMetadataRequest { - NSMutableArray *_results; - NSMutableURLRequest *_request; - NSURLConnection *_connection; - NSMutableData *_data; - NSTimeInterval _delay; - NSTimeInterval _startTime; -} - -@synthesize delay = _delay; - -+ (NSString *)hostname { - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DebugMode"]) { - return PHYSICALWEB_SERVER_HOSTNAME_DEV; - } else { - return PHYSICALWEB_SERVER_HOSTNAME; - } -} - -- (id)init { - self = [super init]; - return self; -} - -- (void)start { - _startTime = [NSDate timeIntervalSinceReferenceDate]; - if ([self isDemo]) { - NSString *urlString = [NSString - stringWithFormat:@"https://%@/demo", [PWMetadataRequest hostname]]; - NSURL *url = [NSURL URLWithString:urlString]; - _request = [[NSMutableURLRequest alloc] initWithURL:url]; - } else { - NSMutableArray *jsonPeripherals = [[NSMutableArray alloc] init]; - for (UBUriBeacon *beacon in [self uriBeacons]) { - NSDictionary *jsonPeripheral = @{ - @"url" : [[beacon URI] absoluteString], - @"rssi" : [NSNumber numberWithLong:(long)[beacon RSSI]], - @"txpower" : [NSNumber numberWithLong:(long)[beacon txPowerLevel]] - }; - [jsonPeripherals addObject:jsonPeripheral]; - } - NSDictionary *jsonBody = @{ @"objects" : jsonPeripherals }; - - NSString *urlString = - [NSString stringWithFormat:@"https://%@/resolve-scan", - [PWMetadataRequest hostname]]; - NSURL *url = [NSURL URLWithString:urlString]; - _request = [[NSMutableURLRequest alloc] initWithURL:url]; - [_request setHTTPMethod:@"POST"]; - [_request setHTTPBody:[NSJSONSerialization dataWithJSONObject:jsonBody - options:0 - error:NULL]]; - } - _connection = - [[NSURLConnection alloc] initWithRequest:_request delegate:self]; - [_connection start]; - _data = [[NSMutableData alloc] init]; -} - -- (void)cancel { - [_connection cancel]; -} - -- (void)_done { - if (_error != nil) { - _results = [[NSMutableArray alloc] init]; - for (UBUriBeacon *uriBeacon in [self uriBeacons]) { - PWBeacon *beacon = - [[PWBeacon alloc] initWithUriBeacon:uriBeacon info:nil]; - [_results addObject:beacon]; - } - } - [[self delegate] metadataRequest_done:self]; -} - -- (NSArray *)results { - return _results; -} - -#pragma mark NSURLConnection delegate - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - _delay = [NSDate timeIntervalSinceReferenceDate] - _startTime; - _connection = nil; - NSError *error; - NSDictionary *result = - [NSJSONSerialization JSONObjectWithData:_data options:0 error:&error]; - _error = error; - if (_error != nil) { - [self _done]; - return; - } - - _results = [[NSMutableArray alloc] init]; - NSArray *list = result[@"metadata"]; - for (NSUInteger i = 0; i < [list count]; i++) { - NSDictionary *item = list[i]; - UBUriBeacon *uriBeacon = nil; - if ([self uriBeacons] != nil) { - uriBeacon = [[self uriBeacons] objectAtIndex:i]; - } - PWBeacon *beacon = [[PWBeacon alloc] initWithUriBeacon:uriBeacon info:item]; - [_results addObject:beacon]; - } - - [self _done]; -} - -- (void)connection:(NSURLConnection *)connection - didFailWithError:(NSError *)error { - _connection = nil; - _error = error; - [self _done]; -} - -- (NSURLRequest *)connection:(NSURLConnection *)connection - willSendRequest:(NSURLRequest *)request - redirectResponse:(NSURLResponse *)redirectResponse { - return request; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - [_data appendData:data]; -} - -@end diff --git a/ios/PhyWeb/Backend/PWURLShortener.h b/ios/PhyWeb/Backend/PWURLShortener.h deleted file mode 100644 index 8f9fddb6..00000000 --- a/ios/PhyWeb/Backend/PWURLShortener.h +++ /dev/null @@ -1,34 +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. - */ - -#import - -typedef void (^PWURLShortenerCompletionBlock)(NSString *shortUrl); - -@interface PWURLShortener : NSObject - -// Shortens a URL using goo.gl. The completion block is called when the URL -// has been shortened. If there's an error, resultURL is be same as URL. -+ (void)shortenURL:(NSURL *)URL - completion:(void (^)(NSError *error, NSURL *resultURL))block; - -// Expands a URL if it's one of the supported URL shorteners. The completion -// block is called when the URL has been expanded. If there's an error, -// resultURL will be the same as URL. -+ (void)expandURL:(NSURL *)URL - completion:(void (^)(NSError *error, NSURL *resultURL))block; - -@end diff --git a/ios/PhyWeb/Backend/PWURLShortener.m b/ios/PhyWeb/Backend/PWURLShortener.m deleted file mode 100644 index 0acfedba..00000000 --- a/ios/PhyWeb/Backend/PWURLShortener.m +++ /dev/null @@ -1,221 +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. - */ - -#import "PWURLShortener.h" - -#import "PWMetadataRequest.h" - -#define REDIRECT_TIMEOUT 10 - -@interface PWInternalURLExpander - : NSObject - -@property(nonatomic, copy) NSURL *URL; - -- (void)startWithCompletionBlock:(void (^)(NSError *error, - NSURL *resultURL))block; - -@end - -@interface PWInternalURLShortener - : NSObject - -@property(nonatomic, copy) NSURL *URL; - -- (void)startWithCompletionBlock:(void (^)(NSError *error, - NSURL *resultURL))block; - -@end - -@implementation PWURLShortener - -+ (void)shortenURL:(NSURL *)URL - completion:(void (^)(NSError *error, NSURL *resultURL))block { - PWInternalURLShortener *shortener = [[PWInternalURLShortener alloc] init]; - [shortener setURL:URL]; - [shortener startWithCompletionBlock:block]; -} - -+ (void)expandURL:(NSURL *)URL - completion:(void (^)(NSError *error, NSURL *resultURL))block { - PWInternalURLExpander *expander = [[PWInternalURLExpander alloc] init]; - [expander setURL:URL]; - [expander startWithCompletionBlock:block]; -} - -@end - -@implementation PWInternalURLShortener { - void (^_completionBlock)(NSError *error, NSURL *resultURL); - NSURLConnection *_connection; - NSInteger _responseCode; - NSMutableData *_responseData; -} - -- (void)startWithCompletionBlock:(void (^)(NSError *error, - NSURL *resultURL))block { - _completionBlock = [block copy]; - _responseData = [NSMutableData data]; - - NSDictionary *postInfo = @{ @"longUrl" : [[self URL] absoluteString] }; - NSData *postData = - [NSJSONSerialization dataWithJSONObject:postInfo options:0 error:NULL]; - NSString *urlString = [NSString - stringWithFormat:@"https://%@/shorten-url", [PWMetadataRequest hostname]]; - NSMutableURLRequest *request = - [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]]; - [request setHTTPMethod:@"POST"]; - [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:postData]; - _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - [_connection start]; -} - -- (void)connection:(NSURLConnection *)connection - didReceiveResponse:(NSURLResponse *)response { - _responseCode = [(NSHTTPURLResponse *)response statusCode]; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - [_responseData appendData:data]; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - NSDictionary *responseInfo = - [NSJSONSerialization JSONObjectWithData:_responseData - options:0 - error:NULL]; - if (![responseInfo isKindOfClass:[NSDictionary class]]) { - [self _doneWithError:nil URL:nil]; - return; - } - NSString *responseURLString = [responseInfo objectForKey:@"id"]; - if (![responseURLString isKindOfClass:[NSString class]]) { - [self _doneWithError:nil URL:nil]; - return; - } - NSURL *responseURL = [NSURL URLWithString:responseURLString]; - // responseURL might be nil if NSURL parser fails. - [self _doneWithError:nil URL:responseURL]; - return; -} - -- (void)connection:(NSURLConnection *)connection - didFailWithError:(NSError *)error { - [self _doneWithError:error URL:nil]; -} - -- (void)_doneWithError:(NSError *)error URL:(NSURL *)responseURL { - if (_completionBlock == nil) { - return; - } - if (responseURL == nil) { - responseURL = [self URL]; - } - _completionBlock(error, responseURL); - _completionBlock = nil; -} - -@end - -@implementation PWInternalURLExpander { - void (^_completionBlock)(NSError *error, NSURL *resultURL); - NSURLConnection *_connection; - NSInteger _responseCode; - NSURL *_redirectedURL; -} - -- (void)startWithCompletionBlock:(void (^)(NSError *error, - NSURL *resultURL))block { - _completionBlock = block; - - if (![self _supportsURL:[self URL]]) { - [self _doneWithError:nil URL:nil]; - return; - } - - NSMutableURLRequest *request = - [[NSMutableURLRequest alloc] initWithURL:[self URL]]; - [request setTimeoutInterval:REDIRECT_TIMEOUT]; - [request setHTTPMethod:@"HEAD"]; - _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - [_connection start]; -} - -- (void)connection:(NSURLConnection *)connection - didReceiveResponse:(NSURLResponse *)response { - _responseCode = [(NSHTTPURLResponse *)response statusCode]; -} - -- (NSURLRequest *)connection:(NSURLConnection *)connection - willSendRequest:(NSURLRequest *)request - redirectResponse:(NSURLResponse *)redirectResponse { - NSInteger responseCode = [(NSHTTPURLResponse *)redirectResponse statusCode]; - if (responseCode == 301) { - _redirectedURL = [request URL]; - [_connection cancel]; - [self _doneWithError:nil URL:_redirectedURL]; - return nil; - } - return request; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - [self _doneWithError:nil URL:nil]; -} - -- (void)connection:(NSURLConnection *)connection - didFailWithError:(NSError *)error { - [self _doneWithError:error URL:nil]; -} - -- (void)_doneWithError:(NSError *)error URL:(NSURL *)responseURL { - if (_completionBlock == nil) { - return; - } - if (responseURL == nil) { - responseURL = [self URL]; - } - _completionBlock(error, responseURL); - _completionBlock = nil; -} - -- (BOOL)_supportsURL:(NSURL *)URL { - NSString *hostname = [[URL host] lowercaseString]; - static NSMutableSet *supported = nil; - @synchronized([self class]) { - if (supported == nil) { - supported = [[NSMutableSet alloc] init]; - [supported addObject:@"t.co"]; - [supported addObject:@"goo.gl"]; - [supported addObject:@"bit.ly"]; - [supported addObject:@"j.mp"]; - [supported addObject:@"bitly.com"]; - [supported addObject:@"amzn.to"]; - [supported addObject:@"fb.com"]; - [supported addObject:@"bit.do"]; - [supported addObject:@"adf.ly"]; - [supported addObject:@"u.to"]; - [supported addObject:@"tinyurl.com"]; - [supported addObject:@"buzurl.com"]; - [supported addObject:@"yourls.org"]; - [supported addObject:@"qr.net"]; - } - } - return [supported containsObject:hostname]; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon-tests/Info.plist b/ios/PhyWeb/Backend/uribeacon-tests/Info.plist deleted file mode 100644 index 640ff945..00000000 --- a/ios/PhyWeb/Backend/uribeacon-tests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.uribeacon.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.h b/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.h deleted file mode 100644 index 7d5ced7d..00000000 --- a/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.h +++ /dev/null @@ -1,24 +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. - */ - -#import - -@interface NSData (UBUnitTest) - -- (NSArray *)ub_unitTestValue; -+ (NSData *)ub_dataWithUnitTestValue:(NSArray *)array; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.m b/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.m deleted file mode 100644 index 30d48147..00000000 --- a/ios/PhyWeb/Backend/uribeacon-tests/NSData+UBUnitTest.m +++ /dev/null @@ -1,67 +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. - */ - -#import "NSData+UBUnitTest.h" - -@implementation NSData (UBUnitTest) - -- (NSArray *)ub_unitTestValue { - NSMutableArray *result = [NSMutableArray array]; - NSMutableString *currentString = nil; - const unsigned char *buf = [self bytes]; - for (NSUInteger i = 0; i < [self length]; ++i) { - // Convert to string data after offset 10. - if ((buf[i] >= 32) && (buf[i] < 127) && (i >= 10)) { - if (currentString == nil) { - currentString = [NSMutableString string]; - } - [currentString appendFormat:@"%c", buf[i]]; - } else { - if (currentString != nil) { - [result addObject:currentString]; - currentString = nil; - } - [result addObject:[NSNumber numberWithInt:buf[i]]]; - } - } - if (currentString != nil) { - [result addObject:currentString]; - currentString = nil; - } - return result; -} - -+ (NSData *)ub_dataWithUnitTestValue:(NSArray *)array { - if (array == nil) { - return nil; - } - if (array == (NSArray *)[NSNull null]) { - return nil; - } - NSMutableData *result = [NSMutableData data]; - for (NSUInteger i = 0; i < [array count]; i++) { - if ([array[i] isKindOfClass:[NSNumber class]]) { - unsigned char value = [array[i] intValue]; - [result appendBytes:&value length:1]; - } else if ([array[i] isKindOfClass:[NSString class]]) { - [result appendData:[(NSString *)array[i] - dataUsingEncoding:NSUTF8StringEncoding]]; - } - } - return result; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon-tests/uribeacon_tests.m b/ios/PhyWeb/Backend/uribeacon-tests/uribeacon_tests.m deleted file mode 100644 index f274188e..00000000 --- a/ios/PhyWeb/Backend/uribeacon-tests/uribeacon_tests.m +++ /dev/null @@ -1,144 +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. - */ - -#import -#import -#import -#import - -#import "NSData+UBUnitTest.h" - -// Redeclare private methods for testing. -@interface UBUriBeacon (Testing) - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - data:(NSData *)data - RSSI:(NSInteger)RSSI; - -- (NSData *)_advertisementData; - -@end - -@interface uribeacon_tests : XCTestCase { - NSDictionary *_tests; -} - -@end - -@implementation uribeacon_tests - -- (void)setUp { - [super setUp]; - NSString *filename = - [[NSBundle bundleForClass:[self class]] pathForResource:@"testdata" - ofType:@"json"]; - NSData *data = [[NSData alloc] initWithContentsOfFile:filename]; - NSError *error = nil; - _tests = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (error != nil) { - NSLog(@"error: %@", error); - } -} - -- (void)tearDown { - [super tearDown]; -} - -- (NSString *)_jsonStringWithScanRecord:(NSArray *)scanRecord { - NSData *jsonData = - [NSJSONSerialization dataWithJSONObject:scanRecord options:0 error:NULL]; - return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; -} - -- (void)testEncode { - NSArray *encodingTests = _tests[@"test-data"]; - unsigned int count = 0; - unsigned int failure = 0; - for (NSDictionary *encodingTest in encodingTests) { - NSURL *url = [[NSURL alloc] initWithString:encodingTest[@"url"]]; - NSNumber *tx = encodingTest[@"tx"]; - if (tx == nil) { - tx = @20; - } - NSNumber *flags = encodingTest[@"flags"]; - if (flags == nil) { - flags = @0; - } - UBUriBeacon *beacon = [[UBUriBeacon alloc] initWithURI:url - txPowerLevel:[tx intValue] - flags:[flags intValue]]; - if (encodingTest[@"scanRecord"] == [NSNull null]) { - XCTAssert(![beacon isValid]); - } - - if (encodingTest[@"scanRecord"] == [NSNull null]) { - XCTAssert(![beacon isValid]); - if ([beacon isValid]) { - failure++; - } - } else { - NSString *scanRecord = - [self _jsonStringWithScanRecord: - [[beacon _advertisementData] ub_unitTestValue]]; - NSString *expectedValue = - [self _jsonStringWithScanRecord:encodingTest[@"scanRecord"]]; - if (![scanRecord isEqualToString:expectedValue]) { - NSLog(@"failed %@", url); - failure++; - } - XCTAssertEqualObjects(scanRecord, expectedValue); - } - count++; - } - NSLog(@"tested %i URLs, %i passed, %i failed", (int)count, - (int)(count - failure), (int)failure); -} - -- (void)testDecode { - NSArray *encodingTests = _tests[@"test-data"]; - unsigned int failure = 0; - unsigned int count = 0; - for (NSDictionary *encodingTest in encodingTests) { - NSURL *url = [[NSURL alloc] initWithString:encodingTest[@"url"]]; - NSNumber *tx = encodingTest[@"tx"]; - if (tx == nil) { - tx = @20; - } - NSNumber *flags = encodingTest[@"flags"]; - if (flags == nil) { - flags = @0; - } - NSData *data = - [NSData ub_dataWithUnitTestValue:encodingTest[@"scanRecord"]]; - if (data != nil) { - UBUriBeacon *beacon = - [[UBUriBeacon alloc] initWithPeripheral:nil data:data RSSI:0]; - if ([[url absoluteString] length] == 0) { - url = nil; - } - if (![url isEqual:[beacon URI]]) { - NSLog(@"failed %@", url); - failure++; - } - XCTAssertEqualObjects(url, [beacon URI]); - count++; - } - } - NSLog(@"tested %i URLs, %i passed, %i failed", (int)[encodingTests count], - (int)([encodingTests count] - failure), (int)failure); -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/NSString+UB.h b/ios/PhyWeb/Backend/uribeacon/NSString+UB.h deleted file mode 100644 index aa0fea75..00000000 --- a/ios/PhyWeb/Backend/uribeacon/NSString+UB.h +++ /dev/null @@ -1,24 +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. - */ - -#import - -@interface NSString (PW) - -+ (NSString *)ub_decodedBeaconURIString:(NSData *)data; -- (NSData *)ub_encodedBeaconURIString; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/NSString+UB.m b/ios/PhyWeb/Backend/uribeacon/NSString+UB.m deleted file mode 100644 index ca879b2b..00000000 --- a/ios/PhyWeb/Backend/uribeacon/NSString+UB.m +++ /dev/null @@ -1,153 +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. - */ - -#import "NSString+UB.h" - -@implementation NSString (PW) - -struct expansion { - char code; - const char *value; - int length; -}; - -static struct expansion prefixExpansionsList[] = { - {0, "http://www."}, - {1, "https://www."}, - {2, "http://"}, - {3, "https://"}, - {4, "urn:uuid:"}, -}; - -static struct expansion expansionsList[] = { - {0, ".com/"}, - {1, ".org/"}, - {2, ".edu/"}, - {3, ".net/"}, - {4, ".info/"}, - {5, ".biz/"}, - {6, ".gov/"}, - {7, ".com"}, - {8, ".org"}, - {9, ".edu"}, - {10, ".net"}, - {11, ".info"}, - {12, ".biz"}, - {13, ".gov"}, -}; - -static const char *prefixExpansionsMapping[255]; -static const char *expansionsMapping[255]; - -+ (NSString *)ub_decodedBeaconURIString:(NSData *)data { - const char *bytes = [data bytes]; - NSUInteger length = [data length]; - NSMutableData *resultData = [NSMutableData data]; - for (unsigned i = 0; i < length; i++) { - const char *expansionValue = NULL; - if (i == 0) { - expansionValue = prefixExpansionsMapping[(unsigned char)bytes[i]]; - } else { - expansionValue = expansionsMapping[(unsigned char)bytes[i]]; - } - if (expansionValue == NULL) { - // TODO(dvh): There's probably room for optimization: several non-encoded - // bytes - // could be appended in one -appendBytes:length: call. - [resultData appendBytes:&bytes[i] length:1]; - } else { - [resultData appendBytes:expansionValue length:strlen(expansionValue)]; - } - if ((i == 0) && (bytes[0] == 4) && (length == sizeof(uuid_t) + 1)) { - uuid_t bytes; - memcpy(&bytes, ((const char *) [data bytes]) + 1, sizeof(uuid_t)); - NSUUID * uuid = [[NSUUID alloc] initWithUUIDBytes:bytes]; - const char * uuidString = [[uuid UUIDString] UTF8String]; - [resultData appendBytes:uuidString length:strlen(uuidString)]; - break; - } - } - - return - [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]; -} - -- (NSData *)ub_encodedBeaconURIString { - // TODO(dvh): There's room for optimization: implementing a trie would help. - NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; - NSMutableData *encodedData = [NSMutableData data]; - unsigned int i = 0; - const char *bytes = [data bytes]; - while (i < [data length]) { - int found = -1; - int foundLength = -1; - struct expansion *table = NULL; - unsigned int tableLength = 0; - if (i == 0) { - table = prefixExpansionsList; - tableLength = - sizeof(prefixExpansionsList) / sizeof(prefixExpansionsList[0]); - } else { - table = expansionsList; - tableLength = sizeof(expansionsList) / sizeof(expansionsList[0]); - } - for (unsigned int k = 0; k < tableLength; k++) { - const char *value = table[k].value; - if (strncmp(bytes + i, value, table[k].length) == 0 && - table[k].length > foundLength) { - found = k; - foundLength = table[k].length; - } - } - if (found != -1) { - char b = (char)found; - [encodedData appendBytes:&b length:1]; - i += table[found].length; - } else { - [encodedData appendBytes:bytes + i length:1]; - i++; - } - if ((i == foundLength) && (found == 4)) { - NSString *uuidString = [[NSString alloc] - initWithData:[data subdataWithRange:NSMakeRange( - foundLength, - [data length] - foundLength)] - encoding:NSUTF8StringEncoding]; - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - uuid_t bytes; - [uuid getUUIDBytes:bytes]; - [encodedData appendBytes:(const char *)&bytes length:sizeof(bytes)]; - break; - } - } - return encodedData; -} - -@end - -__attribute__((constructor)) static void initialize() { - // Build the mapping for text expansions. - for (unsigned int i = 0; - i < sizeof(expansionsList) / sizeof(expansionsList[0]); i++) { - expansionsMapping[expansionsList[i].code] = expansionsList[i].value; - expansionsList[i].length = (int) strlen(expansionsList[i].value); - } - for (unsigned int i = 0; - i < sizeof(prefixExpansionsList) / sizeof(prefixExpansionsList[0]); i++) { - prefixExpansionsMapping[prefixExpansionsList[i].code] = prefixExpansionsList[i].value; - prefixExpansionsList[i].length = (int) strlen(prefixExpansionsList[i].value); - } -} diff --git a/ios/PhyWeb/Backend/uribeacon/NSURL+UB.h b/ios/PhyWeb/Backend/uribeacon/NSURL+UB.h deleted file mode 100644 index 2eb707db..00000000 --- a/ios/PhyWeb/Backend/uribeacon/NSURL+UB.h +++ /dev/null @@ -1,24 +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. - */ - -#import - -@interface NSURL (PW) - -+ (NSURL *)ub_decodedBeaconURI:(NSData *)data; -- (NSData *)ub_encodedBeaconURI; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/NSURL+UB.m b/ios/PhyWeb/Backend/uribeacon/NSURL+UB.m deleted file mode 100644 index 225773f6..00000000 --- a/ios/PhyWeb/Backend/uribeacon/NSURL+UB.m +++ /dev/null @@ -1,34 +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. - */ - -#import "NSURL+UB.h" - -#import "NSString+UB.h" - -@implementation NSURL (PW) - -+ (NSURL *)ub_decodedBeaconURI:(NSData *)data { - if (data == nil) { - return nil; - } - return [NSURL URLWithString:[NSString ub_decodedBeaconURIString:data]]; -} - -- (NSData *)ub_encodedBeaconURI { - return [[self absoluteString] ub_encodedBeaconURIString]; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBConfigurableURIBeaconPrivate.h b/ios/PhyWeb/Backend/uribeacon/UBConfigurableURIBeaconPrivate.h deleted file mode 100644 index ec253065..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBConfigurableURIBeaconPrivate.h +++ /dev/null @@ -1,33 +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. - */ - -#import "UBConfigurableUriBeacon.h" - -#import - -@class UBUriBeaconScanner; - -@interface UBConfigurableUriBeacon (Private) - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI; - -@property(nonatomic, weak) UBUriBeaconScanner *scanner; - -- (void)_updateWithConfigurableBeacon:(UBConfigurableUriBeacon *)beacon; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.h b/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.h deleted file mode 100644 index 2d508adb..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.h +++ /dev/null @@ -1,83 +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. - */ - -#import - -@class UBUriBeacon; - -/** - This class represents a configurable UriBeacon. - - Here's an example of how to configure a beacon: -
// Write beacon data.
- - (void)writeURI:(NSURL *)URI
-        forBeacon:(UBConfigurableUriBeacon *)configurableBeacon {
-   UBUriBeacon *beacon = [[UBUriBeacon alloc] initWithURI:URI
-   txPowerLevel:32];
-   [configurableBeacon writeBeacon:beacon
-                   completionBlock:^(NSError *error) {
-                       if (error != nil) {
-                         NSLog(@"An error happened: %@", error);
-                       } else {
-                         NSLog(@"The beacon has been successfully
-                         configured.");
-                       }
-                   }];
- }
- 
-*/ -@interface UBConfigurableUriBeacon : NSObject - -/** iOS bluetooth device identifier. */ -@property(nonatomic, copy) NSUUID* identifier; - -/** RSSI value. */ -@property(nonatomic, assign) NSInteger RSSI; - -/** - * To hold a beacon in configuration mode, you need to connect to it. - * - * @param block The block will be called when the connection is established. - */ -- (void)connect:(void (^)(NSError* error))block; - -/** - * Disconnects when configuration is done to reflect the changes. - * - * @param block The block will be called when the connection is terminated. - */ -- (void)disconnect:(void (^)(NSError* error))block; - -/** - * Writes beacon advertisement data. The beacon must be connected before writing - * to it. - * - * @param beacon The beacon information to write. - * @param block The block will be called when the data have been written. - */ -- (void)writeBeacon:(UBUriBeacon*)beacon - completionBlock:(void (^)(NSError* error))block; - -/** - * Reads beacon advertisement data. The beacon must be connected before reading - * it. - * - * @param block The block will be called when the data have been read. - */ -- (void)readBeaconWithCompletionBlock:(void (^)(NSError* error, - UBUriBeacon* beacon))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.m b/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.m deleted file mode 100644 index 1c7d6ce2..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBConfigurableUriBeacon.m +++ /dev/null @@ -1,134 +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. - */ - -#import "UBConfigurableUriBeacon.h" -#import "UBConfigurableUriBeaconPrivate.h" - -#import "UBUriBeaconScanner.h" -#import "UBUriBeaconScannerPrivate.h" -#import "UBUriBeacon.h" -#import "UBUriBeaconPrivate.h" -#import "NSURL+UB.h" - -enum { - URIBEACON_VERSION_NONE = 0, - URIBEACON_V1 = 1, - URIBEACON_V2 = 2, -}; - -@implementation UBConfigurableUriBeacon { - UBUriBeaconScanner *_scanner; - CBPeripheral *_peripheral; - int _version; -} - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI { - self = [super init]; - if (!self) { - return nil; - } - NSArray *serviceUUIDS = - [advertisementData objectForKey:CBAdvertisementDataServiceUUIDsKey]; - if ([serviceUUIDS containsObject:[CBUUID UUIDWithString:CONFIG_V2_SERVICE]]) { - _version = URIBEACON_V2; - } else if ([serviceUUIDS - containsObject:[CBUUID UUIDWithString:CONFIG_V1_SERVICE]]) { - _version = URIBEACON_V1; - } else { - return nil; - } - _peripheral = peripheral; - [self setIdentifier:[peripheral identifier]]; - [self setRSSI:[RSSI intValue]]; - return self; -} - -- (void)setScanner:(UBUriBeaconScanner *)scanner { - _scanner = scanner; -} - -- (UBUriBeaconScanner *)scanner { - return _scanner; -} - -- (BOOL)isEqual:(id)object { - UBConfigurableUriBeacon *otherBeacon = object; - return [[self identifier] isEqual:[otherBeacon identifier]] && - ([self RSSI] == [otherBeacon RSSI]); -} - -- (void)connect:(void (^)(NSError *error))block { - [[self scanner] _connectBeaconWithPeripheral:_peripheral - completionBlock:block]; -} - -- (void)disconnect:(void (^)(NSError *error))block { - [[self scanner] _disconnectBeaconWithPeripheral:_peripheral - completionBlock:block]; -} - -- (void)writeBeacon:(UBUriBeacon *)beacon - completionBlock:(void (^)(NSError *error))block { - if (_version == URIBEACON_V1) { - [[self scanner] _writeBeaconWithPeripheral:_peripheral - advertisementData:[beacon _advertisementData] - completionBlock:block]; - } else if (_version == URIBEACON_V2) { - [[self scanner] _writeURIv2WithPeripheral:_peripheral - url:[beacon URI] - completionBlock:block]; - } else { - NSAssert(0, @"invalid configurable beacon"); - } -} - -- (void)readBeaconWithCompletionBlock:(void (^)(NSError *error, - UBUriBeacon *beacon))block { - if (_version == URIBEACON_V1) { - [[self scanner] _readBeaconWithPeripheral:_peripheral - completionBlock:^(NSError *error, NSData *data) { - UBUriBeacon *beacon = [[UBUriBeacon alloc] - initWithPeripheral:_peripheral - data:data - RSSI:[self RSSI]]; - block(error, beacon); - }]; - } else if (_version == URIBEACON_V2) { - [[self scanner] - _readURIv2WithPeripheral:_peripheral - completionBlock:^(NSError *error, NSURL *uri) { - UBUriBeacon *beacon = - [[UBUriBeacon alloc] initWithURI:uri txPowerLevel:0]; - block(error, beacon); - }]; - } else { - NSAssert(0, @"invalid configurable beacon"); - } -} - -- (void)_updateWithConfigurableBeacon:(UBConfigurableUriBeacon *)beacon { - _peripheral = beacon->_peripheral; - [self setRSSI:[beacon RSSI]]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p %@ %li>", [self class], self, - [self identifier], (long)[self RSSI]]; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.h deleted file mode 100644 index 380ce01c..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.h +++ /dev/null @@ -1,80 +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. - */ - -#import - -// Flags of UI Beacons. -typedef NSUInteger UBUriBeaconFlags; - -typedef enum UBUriBeaconRegion { - UBUriBeaconRegionNear, /* between 0 and 0.5 meter */ - UBUriBeaconRegionMid, /* between 0.5 and 2 meters */ - UBUriBeaconRegionFar, /* larger than 2 meters */ - UBUriBeaconRegionUnknown, /* distance could not be determined */ -} UBUriBeaconRegion; - -/** - * UBUriBeacon holds the decoded content of a UriBeacon and some information - * related to the physical device such as the identifier and the received signal - * strength indicator. - */ -@interface UBUriBeacon : NSObject - -/** - * Initializes a UBUriBeacon with a URI and a transmit power. - * - * @param URI URI of the beacon. - * @param txPowerLevel Transmit power level of the beacon. - */ -- (id)initWithURI:(NSURL*)URI txPowerLevel:(int)txPowerLevel; - -/** - * Initializes a UBUriBeacon with a URI, a transmit power and flags. - * - * @param URI URI of the beacon. - * @param txPowerLevel Transmit power level of the beacon. - * @param flags Flags of the beacon. It should be set to 0. - */ -- (id)initWithURI:(NSURL*)URI - txPowerLevel:(int)txPowerLevel - flags:(UBUriBeaconFlags)flags; - -/** iOS bluetooth device identifier. */ -@property(nonatomic, copy) NSUUID* identifier; - -/** - * When UBUriBeacon is returned from `-[UBUriBeaconScanner beacons]`, - * the value is the current received signal strength indicator of the - * related bluetooth device, in decibels. - */ -@property(nonatomic, assign) NSInteger RSSI; - -/** URI of the beacon. */ -@property(nonatomic, copy) NSURL* URI; - -/** Transmit power level. */ -@property(nonatomic, assign) int txPowerLevel; - -/** Flags. The flags need to be set to 0. */ -@property(nonatomic, assign) UBUriBeaconFlags flags; - -/** returns YES if it's going to fit in a bluetooth LE advertisement packet. */ -- (BOOL)isValid; - -/** returns the region of the beacon */ -- (UBUriBeaconRegion)region; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.m b/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.m deleted file mode 100644 index 39bf4f9e..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeacon.m +++ /dev/null @@ -1,254 +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. - */ - -#import "UBUriBeacon.h" -#import "UBUriBeaconPrivate.h" - -#import "NSURL+UB.h" - -enum { - SERVICE_TYPE_URIBEACON, - SERVICE_TYPE_EDDYSTONE, -}; - -@implementation UBUriBeacon - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI { - self = [super init]; - if (!self) { - return nil; - } - NSDictionary *info = - [advertisementData objectForKey:CBAdvertisementDataServiceDataKey]; - if (info == nil) { - // No service data. - return nil; - } - int type = SERVICE_TYPE_URIBEACON; - NSData *data = [info objectForKey:[CBUUID UUIDWithString:URIBEACON_SERVICE]]; - if (data == nil) { - // No UriBeacon service data. - type = SERVICE_TYPE_EDDYSTONE; - data = [info objectForKey:[CBUUID UUIDWithString:EDDYSTONE_SERVICE]]; - if (data == nil) { - // No service data. - return nil; - } - } - if (![self _parseFromData:data type:type]) { - // Data couldn't be parsed. - return nil; - } - [self setIdentifier:[peripheral identifier]]; - [self setRSSI:[RSSI intValue]]; - - return self; -} - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - data:(NSData *)data - RSSI:(NSInteger)RSSI { - self = [super init]; - if (!self) { - return nil; - } - if ([data length] < 8) { - return nil; - } - if (![self _parseFromData:[data subdataWithRange:NSMakeRange( - 8, [data length] - 8)] - type:SERVICE_TYPE_URIBEACON]) { - // Data couldn't be parsed. - return nil; - } - [self setIdentifier:[peripheral identifier]]; - [self setRSSI:RSSI]; - - return self; -} - -- (id)initWithURI:(NSURL *)URI txPowerLevel:(int)txPowerLevel { - return [self initWithURI:URI txPowerLevel:txPowerLevel flags:0]; -} - -- (id)initWithURI:(NSURL *)URI - txPowerLevel:(int)txPowerLevel - flags:(UBUriBeaconFlags)flags { - self = [super init]; - if (!self) { - return nil; - } - [self setURI:URI]; - [self setTxPowerLevel:txPowerLevel]; - [self setFlags:flags]; - [self setRSSI:127]; - return self; -} - -- (BOOL)_parseFromData:(NSData *)data type:(int)type { - size_t length = [data length]; - if (length <= 2) { - return NO; - } - int packetType = ((unsigned char *)[data bytes])[0]; - if (type == SERVICE_TYPE_EDDYSTONE && ((packetType & 0xf0) != 0x10)) { - return NO; - } - [self setFlags:((unsigned char *)[data bytes])[0]]; - [self setTxPowerLevel:((char *)[data bytes])[1]]; - NSData *encodedURI = [data subdataWithRange:NSMakeRange(2, length - 2)]; - [self setURI:[NSURL ub_decodedBeaconURI:encodedURI]]; - return YES; -} - -- (BOOL)isEqual:(id)object { - UBUriBeacon *otherBeacon = object; - return [[self identifier] isEqual:[otherBeacon identifier]] && - ([self RSSI] == [otherBeacon RSSI]) && - [[self URI] isEqual:[otherBeacon URI]] && - ([self txPowerLevel] == [otherBeacon txPowerLevel]) && - [self flags] == [otherBeacon flags]; -} - -- (NSData *)_advertisementData { - static char advdata[] = { - 0x03, // length - 0x03, // Service ID - 0xD8, - 0xFE, // UUID - 0x0, // length - 0x16, // Service Data - 0xD8, - 0xFE, // UUID - 0x0, // flags - 0x0, // Transmit power - }; - - NSMutableData *result = [NSMutableData data]; - [result appendBytes:advdata length:sizeof(advdata)]; - NSData *encodedURI = [[self URI] ub_encodedBeaconURI]; - [result appendData:encodedURI]; - unsigned char *bytes = [result mutableBytes]; - bytes[4] = [encodedURI length] + 5; - bytes[8] = [self flags]; - bytes[9] = [self txPowerLevel]; - - if ([result length] > 28) { - // URL is too long to fit in the packet. - return nil; - } - - return result; -} - -- (void)_updateWithBeacon:(UBUriBeacon *)beacon { - [self setRSSI:[beacon RSSI]]; - [self setURI:[beacon URI]]; - [self setTxPowerLevel:[beacon txPowerLevel]]; - [self setFlags:[beacon flags]]; -} - -- (BOOL)isValid { - return [self _advertisementData] != nil; -} - -/* - * Key to variable names used in this class (viz. Physics): - * - * c = speed of light (2.9979 x 10^8 m/s); - * - * f = frequency (Bluetooth frequency is 2.45GHz = 2.45x10^9 Hz); - * - * l = wavelength (meters); - * - * d = distance (from transmitter to receiver in meters); - * - * - * Free-space path loss (FSPL) is proportional to the square of the distance - * between the transmitter and the receiver, and also proportional to the square - *of the frequency of the radio signal. - * - * FSPL = (4 * pi * d / l)^2 = (4 * pi * d * f / c )^2 - * - * FSPL (dBm) = 20*log10(d) + 20*log10(f) + 20*log10(4*pi/c) = 20*log10(d) + - * PATH_LOSS_AT_1M - * - * Calculating constants: - * - * FSPL_FREQ = 20*log10(f) = 20*log10(2.45 * 10^9) = 188.78 [round to 189] - * - * FSPL_LIGHT = 20*log10(4*pi/c) = 20*log10(4pi/2.9979*10^8) = -147.55 - * [round to -148] - * - * PATH_LOSS_AT_1M = FSPL_FREQ + FSPL_LIGHT = 188.78 - 147.55 = 41.23 - * [round to 41] - * - * - * Re-arranging formula to provide a solution for distance when the path loss - * (FPSL) is available: - * - * 20*log10(d) = path loss - PATH_LOSS_AT_1M - * - * distance(d) = 10^((path loss - PATH_LOSS_AT_1M)/20.0) - * - * The beacon will broadcast its power as it would be seen in ideal conditions - * at 1 meter, computed using the following equation from its own source power. - * - * calibratedTxPower = txPowerAtSource - path loss at 1m (for BLE 1m path loss - * is 41dBm) - */ - -// Free Space Path Loss (FSPL) Constants (see above) -#define FSPL_FREQ 189 -#define FSPL_LIGHT -148 - -/* (dBm) PATH_LOSS at 1m for isotropic antenna transmitting BLE */ -#define FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE \ - (FSPL_FREQ + FSPL_LIGHT) // const = 41 - -// Cutoff distances between different regions. -#define NEAR_TO_MID_METERS 0.5 -#define MID_TO_FAR_METERS 2.0 - -- (UBUriBeaconRegion)region { - NSInteger txPowerAtSource = [self txPowerLevel]; - NSInteger pathLoss = txPowerAtSource - [self RSSI]; - // Distance calculation - double distance = - pow(10.0, (pathLoss - FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE) / 20.0); - - if (distance < 0) { - return UBUriBeaconRegionUnknown; - } - if (distance <= NEAR_TO_MID_METERS) { - return UBUriBeaconRegionNear; - } - if (distance <= MID_TO_FAR_METERS) { - return UBUriBeaconRegionMid; - } - return UBUriBeaconRegionFar; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p %@ %@ %lu %i %li>", [self class], - self, [self identifier], [self URI], - (unsigned long)[self flags], - [self txPowerLevel], (long)[self RSSI]]; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconPrivate.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconPrivate.h deleted file mode 100644 index 68893ae9..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconPrivate.h +++ /dev/null @@ -1,62 +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. - */ - -#import "UBUriBeacon.h" - -#import - -#define URIBEACON_SERVICE @"FED8" -#define EDDYSTONE_SERVICE @"FEAA" - -#define CONFIG_V1_SERVICE @"b35d7da6-eed4-4d59-8f89-f6573edea967" -// ^ - -#define CONFIG_V1_CHARACTERISTIC_DATA1 @"b35d7da7-eed4-4d59-8f89-f6573edea967" -// ^ - -#define CONFIG_V1_CHARACTERISTIC_DATA2 @"b35d7da8-eed4-4d59-8f89-f6573edea967" -// ^ - -#define CONFIG_V1_CHARACTERISTIC_DATASIZE @"b35d7da9-eed4-4d59-8f89-f6573edea967" -// ^ - -#define CONFIG_V2_SERVICE @"ee0c2080-8786-40ba-ab96-99b91ac981d8" - -#define CONFIG_V2_CHARACTERISTIC_LOCKSTATE @"ee0c2081-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_LOCK @"ee0c2082-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_UNLOCK @"ee0c2083-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_URI @"ee0c2084-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_FLAGS @"ee0c2085-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_ADVERTIVEDTXPOWERLEVELS @"ee0c2086-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_TXPOWERMODE @"ee0c2087-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_PERIOD @"ee0c2088-8786-40ba-ab96-99b91ac981d8" -#define CONFIG_V2_CHARACTERISTIC_RESET @"ee0c2089-8786-40ba-ab96-99b91ac981d8" - -@interface UBUriBeacon (Private) - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI; - -- (id)initWithPeripheral:(CBPeripheral *)peripheral - data:(NSData *)data - RSSI:(NSInteger)RSSI; - -- (NSData *)_advertisementData; - -- (void)_updateWithBeacon:(UBUriBeacon *)beacon; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.h deleted file mode 100644 index 751d6892..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.h +++ /dev/null @@ -1,26 +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. - */ - -#import -#import - -@interface UBUriBeaconReader : NSObject - -@property(nonatomic, retain) CBPeripheral *peripheral; - -- (void)readWithCompletionBlock:(void (^)(NSError *error, NSData *data))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.m b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.m deleted file mode 100644 index 2de6c18b..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconReader.m +++ /dev/null @@ -1,144 +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. - */ - -#import "UBUriBeaconReader.h" - -#include "UBUriBeaconPrivate.h" - -enum { - NEED_READ_DATASIZE, - NEED_READ_DATA1, - NEED_READ_DATA2, -}; - -@interface UBUriBeaconReader () - -@end - -@implementation UBUriBeaconReader { - int _state; - NSMutableDictionary *_characteristics; - void (^_completionBlock)(NSError *error, NSData *data); - NSUInteger _length; - NSMutableData *_result; -} - -- (id)init { - self = [super init]; - if (!self) { - return nil; - } - - _state = NEED_READ_DATA1; - - return self; -} - -- (void)dealloc { - [_peripheral setDelegate:nil]; -} - -- (void)readWithCompletionBlock:(void (^)(NSError *error, NSData *data))block { - _completionBlock = [block copy]; - - [_peripheral setDelegate:self]; - [_peripheral discoverServices:@[ [CBUUID UUIDWithString:CONFIG_V1_SERVICE] ]]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverServices:(NSError *)error { - if (error != nil) { - [self _readDoneWithError:error]; - return; - } - - CBService *service = [[peripheral services] objectAtIndex:0]; - [peripheral discoverCharacteristics: - @[ - [CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA1], - [CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA2], - [CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATASIZE] - ] forService:service]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverCharacteristicsForService:(CBService *)service - error:(NSError *)error { - if (error != nil) { - [self _readDoneWithError:error]; - return; - } - - _characteristics = [NSMutableDictionary dictionary]; - for (CBCharacteristic *characteristic in [service characteristics]) { - [_characteristics setObject:characteristic forKey:[characteristic UUID]]; - } - - _result = [NSMutableData data]; - _state = NEED_READ_DATASIZE; - CBCharacteristic *c = [_characteristics - objectForKey:[CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATASIZE]]; - [peripheral readValueForCharacteristic:c]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { - if (error != nil) { - [self _readDoneWithError:error]; - return; - } - - NSData *data = nil; - switch (_state) { - case NEED_READ_DATASIZE: - data = [characteristic value]; - if ([data length] > 0) { - _length = ((const char *)[data bytes])[0]; - } - if (_length == 0) { - [self _readDoneWithError:nil]; - } else { - _state = NEED_READ_DATA1; - CBCharacteristic *c = [_characteristics - objectForKey:[CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA1]]; - [peripheral readValueForCharacteristic:c]; - } - break; - case NEED_READ_DATA1: - [_result appendData:[characteristic value]]; - if (_length <= 20) { - [self _readDoneWithError:nil]; - } else { - _state = NEED_READ_DATA2; - CBCharacteristic *c = [_characteristics - objectForKey:[CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA2]]; - [peripheral readValueForCharacteristic:c]; - } - break; - case NEED_READ_DATA2: - [_result appendData:[characteristic value]]; - [self _readDoneWithError:nil]; - break; - } -} - -- (void)_readDoneWithError:(NSError *)error { - [_peripheral setDelegate:nil]; - _completionBlock(error, _result); -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.h deleted file mode 100644 index 03086be8..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.h +++ /dev/null @@ -1,65 +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. - */ - -#import -#import - -@class UBUriBeacon; -@class UBConfigurableUriBeacon; - -/** - UBUriBeaconScanner will scan for UriBeacons and configurable UriBeacons. - It will scan for beacons when the application is running in the background if - the application supports it. You'll need to add the background capability - `bluetooth-central` in the Info.plist file. When it's scanning in background, - only non-configurable beacons will be discovered. - - Here's an example of how to scan beacons: -
_scanner = [[UBUriBeaconScanner alloc]
-     initWithApplication:[UIApplication sharedApplication]];
- [_scanner startScanningWithUpdateBlock:^{
-   NSLog(@"beacons: %@", [_scanner beacons]);
-   NSLog(@"configurable beacons: %@", [_scanner configurableBeacons]);
- }];
- 
- */ -@interface UBUriBeaconScanner : NSObject - -/** - * Initializes the scanner. - * The -[UIApplication sharedApplication] should be given as argument. - * If you're running in an extension, you should use nil. - */ -- (id)initWithApplication:(UIApplication*)application; - -/** - * Start discovering UriBeacons. It will check frequently for beacon - * to know whether some appear or disappear. - * @param block The block will be called when there's a change in the list of - * beacons or configurable beacons. - */ -- (void)startScanningWithUpdateBlock:(void (^)(void))block; - -/** Stop discovery of UriBeacons and configurable UriBeacons. */ -- (void)stopScanning; - -/** Returns the list of nearby UriBeacons. */ -- (NSArray* /* UBUriBeacon */)beacons; - -/** Returns the list of UriBeacons that can be configured. */ -- (NSArray* /* UBConfigurableUriBeacon */)configurableBeacons; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.m b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.m deleted file mode 100644 index 7aac245b..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScanner.m +++ /dev/null @@ -1,639 +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. - */ - -#import "UBUriBeaconScanner.h" -#import "UBUriBeaconScannerPrivate.h" - -#import "UBConfigurableUriBeacon.h" -#import "UBConfigurableUriBeaconPrivate.h" -#import "UBUriBeacon.h" -#import "UBUriBeaconPrivate.h" -#import "UBUriBeaconReader.h" -#import "UBUriBeaconWriter.h" -#import "UBUriReader.h" -#import "UBUriWriter.h" -#import "NSURL+UB.h" - -#import -#import - -#define SCAN_DELAY 4 -#define PAUSE_DELAY 2 - -// When a beacon is connected, we have to stop scanning to avoid disconnection -// of the device. -// Therefore, when a beacon is connected, we act like bluetooth was turned off. - -enum { - STATE_ASSERT, - STATE_IDLE_OFF, // When the beacon browser is idle and bluetooth is turned - // off. - STATE_IDLE_ON, // When the beacon browser is idle and bluetooth is turned on. - STATE_STARTED, // Browing the nearby beacons has been requested but it's not - // scanning because bluetooth is turned off. - STATE_SCANNING, // It's currently scanning for nearby beacons. - STATE_PAUSED, // It's currently paused scanning the nearby beacons. - STATE_INACTIVE_IDLE_OFF, // Same as STATE_IDLE_OFF but the application is in - // the background. - STATE_INACTIVE_IDLE_ON, // Same as STATE_IDLE_ON but the application is in - // the background. - STATE_INACTIVE_STARTED, // Same as STATE_STARTED but the application is in - // the background. - STATE_INACTIVE_SCANNING, // Same as STATE_SCANNING but the application is in - // the background. -}; - -enum { - TRANSITION_ON, // Bluetooth has been turned on or the last connected device - // disconnected. - TRANSITION_OFF, // Bluetooth has been turned off or a device has been - // connected. - TRANSITION_START, // Start browing has been requested through the API. - TRANSITION_STOP, // Stop browsing has been requested through the API. - TRANSITION_SCAN_DELAY_EXPIRED, // Scan has been performed during [SCAN_DELAY] - // sec and it's done. - TRANSITION_PAUSE_DELAY_EXPIRED, // Scan has been paused during [PAUSE_DELAY] - // sec. - TRANSITION_BECOME_ACTIVE, // The application became active. - TRANSITION_RESIGN_ACTIVE, // The application is now in the background. -}; - -// Below, we define all state transitions in the state machine of the beacon -// browser. - -typedef struct { - int originState; - int transition; - int destinationState; -} stateChange; - -static stateChange stateGraph[] = { - {STATE_IDLE_OFF, TRANSITION_ON, STATE_IDLE_ON}, - {STATE_IDLE_OFF, TRANSITION_START, STATE_STARTED}, - {STATE_IDLE_OFF, TRANSITION_RESIGN_ACTIVE, STATE_INACTIVE_IDLE_OFF}, - {STATE_IDLE_ON, TRANSITION_OFF, STATE_IDLE_OFF}, - {STATE_IDLE_ON, TRANSITION_START, - STATE_SCANNING}, // start scanning, set timer - {STATE_IDLE_ON, TRANSITION_RESIGN_ACTIVE, STATE_INACTIVE_IDLE_ON}, - {STATE_STARTED, TRANSITION_STOP, STATE_IDLE_OFF}, - {STATE_STARTED, TRANSITION_ON, - STATE_SCANNING}, // start scanning, set timer - {STATE_STARTED, TRANSITION_RESIGN_ACTIVE, STATE_INACTIVE_STARTED}, - {STATE_STARTED, TRANSITION_BECOME_ACTIVE, - STATE_STARTED}, // if -startScanningWithUpdateBlock: is called in - // -applicationDidBecomeActive: - {STATE_SCANNING, TRANSITION_STOP, STATE_IDLE_ON}, - {STATE_SCANNING, TRANSITION_OFF, STATE_STARTED}, // stop scanning - {STATE_SCANNING, TRANSITION_SCAN_DELAY_EXPIRED, - STATE_PAUSED}, // stop scanning, set timer - {STATE_SCANNING, TRANSITION_RESIGN_ACTIVE, - STATE_INACTIVE_SCANNING}, // stop scanning, start scanning - {STATE_PAUSED, TRANSITION_STOP, STATE_IDLE_ON}, - {STATE_PAUSED, TRANSITION_OFF, STATE_STARTED}, - {STATE_PAUSED, TRANSITION_PAUSE_DELAY_EXPIRED, - STATE_SCANNING}, // start scanning, set timer - {STATE_PAUSED, TRANSITION_RESIGN_ACTIVE, - STATE_INACTIVE_SCANNING}, // start scanning - {STATE_INACTIVE_IDLE_OFF, TRANSITION_ON, STATE_INACTIVE_IDLE_ON}, - {STATE_INACTIVE_IDLE_OFF, TRANSITION_START, STATE_INACTIVE_STARTED}, - {STATE_INACTIVE_IDLE_OFF, TRANSITION_BECOME_ACTIVE, STATE_IDLE_OFF}, - {STATE_INACTIVE_IDLE_ON, TRANSITION_OFF, STATE_INACTIVE_IDLE_OFF}, - {STATE_INACTIVE_IDLE_ON, TRANSITION_START, - STATE_INACTIVE_SCANNING}, // start scanning - {STATE_INACTIVE_IDLE_ON, TRANSITION_BECOME_ACTIVE, STATE_IDLE_ON}, - {STATE_INACTIVE_STARTED, TRANSITION_STOP, STATE_INACTIVE_IDLE_OFF}, - {STATE_INACTIVE_STARTED, TRANSITION_ON, - STATE_INACTIVE_SCANNING}, // start scanning - {STATE_INACTIVE_STARTED, TRANSITION_BECOME_ACTIVE, STATE_STARTED}, - {STATE_INACTIVE_SCANNING, TRANSITION_STOP, STATE_INACTIVE_IDLE_ON}, - {STATE_INACTIVE_SCANNING, TRANSITION_OFF, - STATE_INACTIVE_STARTED}, // stop scanning - {STATE_INACTIVE_SCANNING, TRANSITION_BECOME_ACTIVE, - STATE_SCANNING}, // stop scanning, start scanning, set timer -}; - -@interface UBUriBeaconScanner () - -@end - -@implementation UBUriBeaconScanner { - CBCentralManager *_beaconsCentralManager; - CBCentralManager *_configurableBeaconsCentralManager; - int _state; - BOOL _bluetoothOnOrConnected; - - NSMutableDictionary *_updatedBeacons; - NSMutableDictionary *_updatedConfigurableBeacons; - NSMutableArray *_beacons; - NSMutableArray *_configurableBeacons; - void (^_updateBlock)(void); - - NSMutableDictionary *_connectionsBlocks; - NSMutableDictionary *_disconnectionsBlocks; - NSMutableDictionary *_writers; - NSMutableDictionary *_readers; - NSMutableSet *_connectedBeacons; - - UIApplication *_application; -} - -- (id)init { - return [self initWithApplication:nil]; -} - -- (id)initWithApplication:(UIApplication *)application { - self = [super init]; - if (!self) { - return nil; - } - _beaconsCentralManager = - [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; - [_beaconsCentralManager setDelegate:self]; - _configurableBeaconsCentralManager = - [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; - [_configurableBeaconsCentralManager setDelegate:self]; - - _application = application; - BOOL applicationActive = - _application != nil - ? [_application applicationState] == UIApplicationStateActive - : YES; - _bluetoothOnOrConnected = - [_beaconsCentralManager state] == CBCentralManagerStatePoweredOn; - if (applicationActive && _bluetoothOnOrConnected) { - _state = STATE_IDLE_ON; - } else if (applicationActive && !_bluetoothOnOrConnected) { - _state = STATE_IDLE_OFF; - } else if (!applicationActive && _bluetoothOnOrConnected) { - _state = STATE_INACTIVE_IDLE_ON; - } else if (!applicationActive && !_bluetoothOnOrConnected) { - _state = STATE_INACTIVE_IDLE_OFF; - } - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(_didBecomeActive:) - name:UIApplicationDidBecomeActiveNotification - object:_application]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(_willResignActive:) - name:UIApplicationWillResignActiveNotification - object:_application]; - _beacons = [NSMutableArray array]; - _configurableBeacons = [NSMutableArray array]; - - _connectionsBlocks = [NSMutableDictionary dictionary]; - _disconnectionsBlocks = [NSMutableDictionary dictionary]; - _writers = [NSMutableDictionary dictionary]; - _readers = [NSMutableDictionary dictionary]; - _connectedBeacons = [NSMutableSet set]; - - return self; -} - -- (void)dealloc { - [self _stopTimers]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)_stopTimers { - [NSObject - cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_timerScanningExpired) - object:nil]; - [NSObject - cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_timerPausingExpired) - object:nil]; -} - -- (void)_startTimerScanning { - [self performSelector:@selector(_timerScanningExpired) - withObject:nil - afterDelay:SCAN_DELAY]; -} - -- (void)_timerScanningExpired { - [self _transition:TRANSITION_SCAN_DELAY_EXPIRED]; -} - -- (void)_startTimerPausing { - [self performSelector:@selector(_timerPausingExpired) - withObject:nil - afterDelay:PAUSE_DELAY]; -} - -- (void)_timerPausingExpired { - [self _transition:TRANSITION_PAUSE_DELAY_EXPIRED]; -} - -- (void)_transition:(int)transition { - [self _stopTimers]; - int previousState = _state; - int state = STATE_ASSERT; - for (unsigned int i = 0; i < sizeof(stateGraph) / sizeof(stateGraph[0]); - i++) { - if ((stateGraph[i].originState == _state) && - (stateGraph[i].transition == transition)) { - state = stateGraph[i].destinationState; - break; - } - } - NSAssert(state != STATE_ASSERT, @"Transition not found in graph %i %i", - _state, transition); - _state = state; - - if ((_state == STATE_SCANNING) || (_state == STATE_INACTIVE_SCANNING)) { - if ((previousState == STATE_SCANNING) || - (previousState == STATE_INACTIVE_SCANNING)) { - [self _stopScanning]; - } - [self _startScanning]; - } else if (_state == STATE_PAUSED) { - [self _stopScanning]; - [self _startTimerPausing]; - } else if ((previousState == STATE_SCANNING) && (_state == STATE_STARTED)) { - [self _stopScanning]; - } else if ((previousState == STATE_INACTIVE_SCANNING) && - (_state == STATE_INACTIVE_STARTED)) { - [self _stopScanning]; - } -} - -- (void)startScanningWithUpdateBlock:(void (^)(void))block { - _updateBlock = [block copy]; - [self _transition:TRANSITION_START]; -} - -- (void)stopScanning { - [self _transition:TRANSITION_STOP]; - _updateBlock = nil; -} - -- (void)_startScanning { - BOOL applicationActive = NO; - switch (_state) { - case STATE_IDLE_OFF: - case STATE_IDLE_ON: - case STATE_STARTED: - case STATE_SCANNING: - case STATE_PAUSED: - applicationActive = YES; - break; - } - _updatedBeacons = [NSMutableDictionary dictionary]; - _updatedConfigurableBeacons = [NSMutableDictionary dictionary]; - if (applicationActive) { - [_beaconsCentralManager scanForPeripheralsWithServices:@[ - [CBUUID UUIDWithString:URIBEACON_SERVICE], - [CBUUID UUIDWithString:EDDYSTONE_SERVICE] - ] options:nil]; - [_configurableBeaconsCentralManager scanForPeripheralsWithServices:@[ - [CBUUID UUIDWithString:CONFIG_V1_SERVICE], - [CBUUID UUIDWithString:CONFIG_V2_SERVICE] - ] options:nil]; - [self _startTimerScanning]; - } else { - NSArray *services = @[ - [CBUUID UUIDWithString:URIBEACON_SERVICE], - [CBUUID UUIDWithString:EDDYSTONE_SERVICE] - ]; - [_beaconsCentralManager scanForPeripheralsWithServices:services - options:nil]; - } -} - -- (void)_stopScanning { - [_beaconsCentralManager stopScan]; - [_configurableBeaconsCentralManager stopScan]; - [self _updateDataWithDeletion:YES]; -} - -- (void)_willResignActive:(NSNotification *)notification { - [self _transition:TRANSITION_RESIGN_ACTIVE]; -} - -- (void)_didBecomeActive:(NSNotification *)notification { - [self _transition:TRANSITION_BECOME_ACTIVE]; -} - -- (void)centralManager:(CBCentralManager *)central - didDiscoverPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI { - if (central == _configurableBeaconsCentralManager) { - UBConfigurableUriBeacon *configurableBeacon = - [[UBConfigurableUriBeacon alloc] initWithPeripheral:peripheral - advertisementData:advertisementData - RSSI:RSSI]; - [configurableBeacon setScanner:self]; - if (configurableBeacon != nil) { - [_updatedBeacons removeObjectForKey:[configurableBeacon identifier]]; - [_updatedConfigurableBeacons setObject:configurableBeacon - forKey:[configurableBeacon identifier]]; - [self _updateData]; - } - } else { - UBUriBeacon *beacon = - [[UBUriBeacon alloc] initWithPeripheral:peripheral - advertisementData:advertisementData - RSSI:RSSI]; - if (beacon != nil) { - [_updatedConfigurableBeacons removeObjectForKey:[beacon identifier]]; - [_updatedBeacons setObject:beacon forKey:[beacon identifier]]; - [self _updateData]; - } - } -} - -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - [self _updateBluetoothState]; -} - -- (void)_updateBluetoothState { - BOOL bluetoothOnOrConnected = - ([_beaconsCentralManager state] == CBCentralManagerStatePoweredOn) && - ([_connectedBeacons count] == 0); - if (_bluetoothOnOrConnected == bluetoothOnOrConnected) { - return; - } - _bluetoothOnOrConnected = bluetoothOnOrConnected; - if (bluetoothOnOrConnected) { - [self _transition:TRANSITION_ON]; - } else { - [self _transition:TRANSITION_OFF]; - } -} - -- (void)_updateData { - [self _updateDataWithDeletion:NO]; -} - -- (void)_updateDataWithDeletion:(BOOL)applyDeletion { - BOOL updated = NO; - - NSMutableDictionary *existingBeacons = [NSMutableDictionary dictionary]; - NSSet *updatedBeaconsIdentifiers = nil; - for (UBUriBeacon *beacon in _beacons) { - [existingBeacons setObject:beacon forKey:[beacon identifier]]; - } - updatedBeaconsIdentifiers = [NSSet setWithArray:[_updatedBeacons allKeys]]; - for (UBUriBeacon *beacon in _beacons) { - if (![updatedBeaconsIdentifiers containsObject:[beacon identifier]]) { - // has deletion. - updated = YES; - break; - } - } - NSMutableArray *beacons = [NSMutableArray array]; - NSMutableSet *beaconsSet = [NSMutableSet set]; - if (!applyDeletion) { - // Add existing beacons in the results. - for (UBUriBeacon *beacon in _beacons) { - // Don't add beacons that are now in configuration mode. - if ([_updatedConfigurableBeacons objectForKey:[beacon identifier]] != - nil) { - continue; - } - UBUriBeacon *updatedBeacon = - [_updatedBeacons objectForKey:[beacon identifier]]; - if (updatedBeacon != nil) { - [beacon _updateWithBeacon:updatedBeacon]; - updated = YES; - } - [beacons addObject:beacon]; - [beaconsSet addObject:[beacon identifier]]; - } - } - // Add updated beacons. - for (UBUriBeacon *beacon in [_updatedBeacons allValues]) { - if ([beaconsSet containsObject:[beacon identifier]]) { - continue; - } - - UBUriBeacon *existingBeacon = - [existingBeacons objectForKey:[beacon identifier]]; - if (existingBeacon != nil) { - [beacons addObject:existingBeacon]; - if (![existingBeacon isEqual:beacon]) { - // Update values. - [existingBeacon _updateWithBeacon:beacon]; - updated = YES; - } - } else { - [beacons addObject:beacon]; - [beaconsSet addObject:[beacon identifier]]; - updated = YES; - } - } - - existingBeacons = [NSMutableDictionary dictionary]; - for (UBConfigurableUriBeacon *beacon in _configurableBeacons) { - [existingBeacons setObject:beacon forKey:[beacon identifier]]; - } - updatedBeaconsIdentifiers = - [NSSet setWithArray:[_updatedConfigurableBeacons allKeys]]; - for (UBConfigurableUriBeacon *beacon in _configurableBeacons) { - if (![updatedBeaconsIdentifiers containsObject:[beacon identifier]]) { - // has deletion. - updated = YES; - break; - } - } - NSMutableArray *configurableBeacons = [NSMutableArray array]; - NSMutableSet *configurableBeaconsSet = [NSMutableSet set]; - if (!applyDeletion) { - // Add existing configurable beacons in the result. - for (UBConfigurableUriBeacon *beacon in _configurableBeacons) { - // Don't add beacons that are not in configuration mode now. - if ([_updatedBeacons objectForKey:[beacon identifier]] != nil) { - continue; - } - UBConfigurableUriBeacon *updatedBeacon = - [_updatedConfigurableBeacons objectForKey:[beacon identifier]]; - if (updatedBeacon != nil) { - [beacon _updateWithConfigurableBeacon:updatedBeacon]; - [_updatedConfigurableBeacons removeObjectForKey:[beacon identifier]]; - updated = YES; - } - [configurableBeacons addObject:beacon]; - [configurableBeaconsSet addObject:[beacon identifier]]; - } - } - // Add updated beacons. - for (UBConfigurableUriBeacon *beacon in - [_updatedConfigurableBeacons allValues]) { - if ([configurableBeaconsSet containsObject:[beacon identifier]]) { - continue; - } - UBConfigurableUriBeacon *existingBeacon = - [existingBeacons objectForKey:[beacon identifier]]; - if (existingBeacon != nil) { - [configurableBeacons addObject:existingBeacon]; - if (![existingBeacon isEqual:beacon]) { - // Update values. - [existingBeacon _updateWithConfigurableBeacon:beacon]; - updated = YES; - } - } else { - [configurableBeacons addObject:beacon]; - [configurableBeaconsSet addObject:[beacon identifier]]; - updated = YES; - } - } - - if (updated) { - NSMutableSet *existingBeaconsIdentifiers = [NSMutableSet set]; - for (UBUriBeacon *beacon in beacons) { - [existingBeaconsIdentifiers addObject:[beacon identifier]]; - } - for (UBConfigurableUriBeacon *beacon in configurableBeacons) { - [existingBeaconsIdentifiers addObject:[beacon identifier]]; - } - - _beacons = beacons; - _configurableBeacons = configurableBeacons; - [self _notify]; - } -} - -- (void)_notify { - if (_updateBlock != nil) { - _updateBlock(); - } -} - -- (NSArray * /* UBUriBeacon */)beacons { - return _beacons; -} - -- (NSArray * /* UBURIConfigurableBeacon */)configurableBeacons { - return _configurableBeacons; -} - -- (void)_connectBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error))block { - [_configurableBeaconsCentralManager connectPeripheral:peripheral options:nil]; - [_connectionsBlocks setObject:block forKey:[peripheral identifier]]; - [_connectedBeacons addObject:[peripheral identifier]]; - [self _updateBluetoothState]; -} - -- (void)centralManager:(CBCentralManager *)central - didConnectPeripheral:(CBPeripheral *)peripheral { - void (^block)(NSError *error) = - [_connectionsBlocks objectForKey:[peripheral identifier]]; - [_connectionsBlocks removeObjectForKey:[peripheral identifier]]; - if (block != nil) { - block(NULL); - } -} - -- (void)_disconnectBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error))block { - [_configurableBeaconsCentralManager cancelPeripheralConnection:peripheral]; - [_disconnectionsBlocks setObject:block forKey:[peripheral identifier]]; - [_connectedBeacons removeObject:[peripheral identifier]]; - // Remove the given configurable beacon. Now it's been disconnected, it should - // switch back to normal mode. - NSUInteger indexToRemove = [_configurableBeacons - indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { - UBConfigurableUriBeacon *beacon = obj; - return [[beacon identifier] isEqual:[peripheral identifier]]; - }]; - if (indexToRemove != NSNotFound) { - [_configurableBeacons removeObjectAtIndex:indexToRemove]; - [self _notify]; - } - [self _updateBluetoothState]; -} - -- (void)centralManager:(CBCentralManager *)central - didDisconnectPeripheral:(CBPeripheral *)peripheral - error:(NSError *)error { - void (^block)(NSError *error) = - [_disconnectionsBlocks objectForKey:[peripheral identifier]]; - [_disconnectionsBlocks removeObjectForKey:[peripheral identifier]]; - if (block != nil) { - block(error); - } -} - -- (void)_writeBeaconWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSData *)data - completionBlock:(void (^)(NSError *error))block { - UBUriBeaconWriter *writer = [[UBUriBeaconWriter alloc] init]; - [writer setPeripheral:peripheral]; - [writer setData:data]; - [_writers setObject:writer forKey:[peripheral identifier]]; - [writer writeWithCompletionBlock:^(NSError *error) { - void (^writeCompletionBlock)(NSError *error) = [block copy]; - [_writers removeObjectForKey:[peripheral identifier]]; - if (writeCompletionBlock != nil) { - writeCompletionBlock(error); - } - }]; -} - -- (void)_readBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock: - (void (^)(NSError *error, NSData *data))block { - UBUriBeaconReader *reader = [[UBUriBeaconReader alloc] init]; - [reader setPeripheral:peripheral]; - [_readers setObject:reader forKey:[peripheral identifier]]; - [reader readWithCompletionBlock:^(NSError *error, NSData *data) { - void (^readCompletionBlock)(NSError *error, NSData *data) = [block copy]; - [_readers removeObjectForKey:[peripheral identifier]]; - if (readCompletionBlock != nil) { - readCompletionBlock(error, data); - } - }]; -} - -- (void)_writeURIv2WithPeripheral:(CBPeripheral *)peripheral - url:(NSURL *)url - completionBlock:(void (^)(NSError *error))block { - UBUriWriter *writer = [[UBUriWriter alloc] init]; - [writer setPeripheral:peripheral]; - [writer setData:[url ub_encodedBeaconURI]]; - [writer setCharacteristic:CONFIG_V2_CHARACTERISTIC_URI]; - [_writers setObject:writer forKey:[peripheral identifier]]; - [writer writeWithCompletionBlock:^(NSError *error) { - void (^writeCompletionBlock)(NSError *error) = [block copy]; - [_writers removeObjectForKey:[peripheral identifier]]; - if (writeCompletionBlock != nil) { - writeCompletionBlock(error); - } - }]; -} - -- (void)_readURIv2WithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error, NSURL *uri))block { - UBUriReader *reader = [[UBUriReader alloc] init]; - [reader setPeripheral:peripheral]; - [reader setCharacteristic:CONFIG_V2_CHARACTERISTIC_URI]; - [_readers setObject:reader forKey:[peripheral identifier]]; - [reader readWithCompletionBlock:^(NSError *error, NSData *data) { - void (^readCompletionBlock)(NSError *error, NSURL *uri) = [block copy]; - [_readers removeObjectForKey:[peripheral identifier]]; - if (readCompletionBlock != nil) { - readCompletionBlock(error, [NSURL ub_decodedBeaconURI:data]); - } - }]; -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScannerPrivate.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScannerPrivate.h deleted file mode 100644 index 9488bf9e..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconScannerPrivate.h +++ /dev/null @@ -1,43 +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. - */ - -#import "UBUriBeaconScanner.h" - -#import - -@interface UBUriBeaconScanner (Private) - -- (void)_connectBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error))block; - -- (void)_disconnectBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error))block; - -- (void)_writeBeaconWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSData *)data - completionBlock:(void (^)(NSError *error))block; - -- (void)_readBeaconWithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error, NSData *data))block; - -- (void)_writeURIv2WithPeripheral:(CBPeripheral *)peripheral - url:(NSURL *)url - completionBlock:(void (^)(NSError *error))block; - -- (void)_readURIv2WithPeripheral:(CBPeripheral *)peripheral - completionBlock:(void (^)(NSError *error, NSURL *uri))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.h b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.h deleted file mode 100644 index 56249b9f..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.h +++ /dev/null @@ -1,27 +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. - */ - -#import -#import - -@interface UBUriBeaconWriter : NSObject - -@property(nonatomic, retain) CBPeripheral *peripheral; -@property(nonatomic, retain) NSData *data; - -- (void)writeWithCompletionBlock:(void (^)(NSError *error))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.m b/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.m deleted file mode 100644 index 2060b40e..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriBeaconWriter.m +++ /dev/null @@ -1,133 +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. - */ - -#import "UBUriBeaconWriter.h" - -#include "UBUriBeaconPrivate.h" - -enum { - NEED_WRITE_DATA1, - NEED_WRITE_DATA2, -}; - -@interface UBUriBeaconWriter () - -@end - -@implementation UBUriBeaconWriter { - int _state; - NSMutableDictionary *_characteristics; - void (^_completionBlock)(NSError *error); - NSUInteger _length; -} - -- (id)init { - self = [super init]; - if (!self) { - return nil; - } - - _state = NEED_WRITE_DATA1; - - return self; -} - -- (void)dealloc { - [_peripheral setDelegate:nil]; -} - -- (void)writeWithCompletionBlock:(void (^)(NSError *error))block { - _completionBlock = [block copy]; - - [_peripheral setDelegate:self]; - [_peripheral discoverServices:@[ [CBUUID UUIDWithString:CONFIG_V1_SERVICE] ]]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverServices:(NSError *)error { - if (error != nil) { - [self _writeDoneWithError:error]; - return; - } - - CBService *service = [[peripheral services] objectAtIndex:0]; - [peripheral discoverCharacteristics: - @[ - [CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA1], - [CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA2] - ] forService:service]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverCharacteristicsForService:(CBService *)service - error:(NSError *)error { - if (error != nil) { - [self _writeDoneWithError:error]; - return; - } - - _characteristics = [NSMutableDictionary dictionary]; - for (CBCharacteristic *characteristic in [service characteristics]) { - [_characteristics setObject:characteristic forKey:[characteristic UUID]]; - } - - CBCharacteristic *c = [_characteristics - objectForKey:[CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA1]]; - _length = [[self data] length]; - - NSData *data = nil; - if (_length > 20) { - data = [[self data] subdataWithRange:NSMakeRange(0, 20)]; - } else { - data = [self data]; - } - [peripheral writeValue:data - forCharacteristic:c - type:CBCharacteristicWriteWithResponse]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didWriteValueForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { - if (error != nil) { - [self _writeDoneWithError:error]; - return; - } - - if (_state == NEED_WRITE_DATA2) { - [self _writeDoneWithError:nil]; - return; - } - - if (_length > 20) { - NSData *data = [[self data] subdataWithRange:NSMakeRange(20, _length - 20)]; - _state = NEED_WRITE_DATA2; - CBCharacteristic *c = [_characteristics - objectForKey:[CBUUID UUIDWithString:CONFIG_V1_CHARACTERISTIC_DATA2]]; - [peripheral writeValue:data - forCharacteristic:c - type:CBCharacteristicWriteWithResponse]; - } else { - [self _writeDoneWithError:nil]; - } -} - -- (void)_writeDoneWithError:(NSError *)error { - [_peripheral setDelegate:nil]; - _completionBlock(error); -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriReader.h b/ios/PhyWeb/Backend/uribeacon/UBUriReader.h deleted file mode 100644 index 2b254054..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriReader.h +++ /dev/null @@ -1,27 +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. - */ - -#import -#import - -@interface UBUriReader : NSObject - -@property(nonatomic, retain) CBPeripheral *peripheral; -@property(nonatomic, copy) NSString *characteristic; - -- (void)readWithCompletionBlock:(void (^)(NSError *error, NSData *data))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriReader.m b/ios/PhyWeb/Backend/uribeacon/UBUriReader.m deleted file mode 100644 index aa1f4898..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriReader.m +++ /dev/null @@ -1,101 +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. - */ - -#import "UBUriReader.h" - -#include "UBUriBeaconPrivate.h" - -@interface UBUriReader () - -@end - -@implementation UBUriReader { - void (^_completionBlock)(NSError *error, NSData *data); - NSData *_data; -} - -- (id)init { - self = [super init]; - if (!self) { - return nil; - } - - return self; -} - -- (void)dealloc { - [_peripheral setDelegate:nil]; -} - -- (void)readWithCompletionBlock:(void (^)(NSError *error, NSData *data))block { - _completionBlock = [block copy]; - - [_peripheral setDelegate:self]; - [_peripheral discoverServices:@[ [CBUUID UUIDWithString:CONFIG_V2_SERVICE] ]]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverServices:(NSError *)error { - if (error != nil) { - [self _readDoneWithError:error]; - return; - } - - CBService *service = nil; - for (CBService *s in [peripheral services]) { - if ([[[s UUID] UUIDString] caseInsensitiveCompare:CONFIG_V2_SERVICE] == - NSOrderedSame) { - service = s; - } - } - - [peripheral discoverCharacteristics:@[ - [CBUUID UUIDWithString:[self characteristic]] - ] forService:service]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverCharacteristicsForService:(CBService *)service - error:(NSError *)error { - if (error != nil) { - [self _readDoneWithError:error]; - return; - } - - CBCharacteristic *c = nil; - for (CBCharacteristic *characteristic in [service characteristics]) { - if ([[[characteristic UUID] UUIDString] - caseInsensitiveCompare:[self characteristic]] == NSOrderedSame) { - c = characteristic; - } - } - - [peripheral readValueForCharacteristic:c]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { - _data = [characteristic value]; - [self _readDoneWithError:error]; -} - -- (void)_readDoneWithError:(NSError *)error { - [_peripheral setDelegate:nil]; - _completionBlock(error, _data); -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriWriter.h b/ios/PhyWeb/Backend/uribeacon/UBUriWriter.h deleted file mode 100644 index 14f2c8f0..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriWriter.h +++ /dev/null @@ -1,28 +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. - */ - -#import -#import - -@interface UBUriWriter : NSObject - -@property(nonatomic, retain) CBPeripheral *peripheral; -@property(nonatomic, retain) NSData *data; -@property(nonatomic, copy) NSString *characteristic; - -- (void)writeWithCompletionBlock:(void (^)(NSError *error))block; - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UBUriWriter.m b/ios/PhyWeb/Backend/uribeacon/UBUriWriter.m deleted file mode 100644 index b2532dfe..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UBUriWriter.m +++ /dev/null @@ -1,101 +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. - */ - -#import "UBUriWriter.h" - -#include "UBUriBeaconPrivate.h" - -@interface UBUriWriter () - -@end - -@implementation UBUriWriter { - void (^_completionBlock)(NSError *error); -} - -- (id)init { - self = [super init]; - if (!self) { - return nil; - } - - return self; -} - -- (void)dealloc { - [_peripheral setDelegate:nil]; -} - -- (void)writeWithCompletionBlock:(void (^)(NSError *error))block { - _completionBlock = [block copy]; - - [_peripheral setDelegate:self]; - [_peripheral discoverServices:@[ [CBUUID UUIDWithString:CONFIG_V2_SERVICE] ]]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverServices:(NSError *)error { - if (error != nil) { - [self _writeDoneWithError:error]; - return; - } - - CBService *service = nil; - for (CBService *s in [peripheral services]) { - if ([[[s UUID] UUIDString] caseInsensitiveCompare:CONFIG_V2_SERVICE] == - NSOrderedSame) { - service = s; - } - } - - [peripheral discoverCharacteristics:@[ - [CBUUID UUIDWithString:[self characteristic]] - ] forService:service]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didDiscoverCharacteristicsForService:(CBService *)service - error:(NSError *)error { - if (error != nil) { - [self _writeDoneWithError:error]; - return; - } - - CBCharacteristic *c = nil; - for (CBCharacteristic *characteristic in [service characteristics]) { - if ([[[characteristic UUID] UUIDString] - caseInsensitiveCompare:[self characteristic]] == NSOrderedSame) { - c = characteristic; - } - } - - [peripheral writeValue:[self data] - forCharacteristic:c - type:CBCharacteristicWriteWithResponse]; -} - -- (void)peripheral:(CBPeripheral *)peripheral - didWriteValueForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { - [self _writeDoneWithError:error]; -} - -- (void)_writeDoneWithError:(NSError *)error { - [_peripheral setDelegate:nil]; - _completionBlock(error); -} - -@end diff --git a/ios/PhyWeb/Backend/uribeacon/UriBeacon.h b/ios/PhyWeb/Backend/uribeacon/UriBeacon.h deleted file mode 100644 index cc7c4905..00000000 --- a/ios/PhyWeb/Backend/uribeacon/UriBeacon.h +++ /dev/null @@ -1,19 +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. - */ - -#import "UBUriBeaconScanner.h" -#import "UBUriBeacon.h" -#import "UBConfigurableUriBeacon.h" diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9602ea5c..00000000 --- a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "images" : [ - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-58.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-87.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-80-1.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-120-1.png", - "scale" : "3x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "icon-57.png", - "scale" : "1x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "icon-114.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-120.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-180.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29-1.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-58-1.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-80.png", - "scale" : "2x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "icon-50.png", - "scale" : "1x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "icon-100.png", - "scale" : "2x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "icon-72.png", - "scale" : "1x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "icon-144.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-152.png", - "scale" : "2x" - }, - { - "idiom" : "car", - "size" : "120x120", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-100.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-100.png deleted file mode 100644 index 7b316d04..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-100.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-114.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-114.png deleted file mode 100644 index fe0be176..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-114.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120-1.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120-1.png deleted file mode 100644 index 4088d1ef..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120-1.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120.png deleted file mode 100644 index 4088d1ef..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-120.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-144.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-144.png deleted file mode 100644 index e104eb0e..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-144.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-152.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-152.png deleted file mode 100644 index a816f18b..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-152.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-180.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-180.png deleted file mode 100644 index 6f4ab30a..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-180.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29-1.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29-1.png deleted file mode 100644 index 7bf4b317..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29-1.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29.png deleted file mode 100644 index 7bf4b317..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-40.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index 20442f38..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-50.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-50.png deleted file mode 100644 index 90821f4d..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-50.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-57.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-57.png deleted file mode 100644 index 78659137..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-57.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58-1.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58-1.png deleted file mode 100644 index f4df1718..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58-1.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58.png deleted file mode 100644 index f4df1718..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-58.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-72.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-72.png deleted file mode 100644 index 80a895f3..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-72.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-76.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index 50ae3f73..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80-1.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80-1.png deleted file mode 100644 index 54ef07b3..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80-1.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80.png deleted file mode 100644 index 54ef07b3..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-80.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-87.png b/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-87.png deleted file mode 100644 index e9294283..00000000 Binary files a/ios/PhyWeb/Images.xcassets/AppIcon.appiconset/icon-87.png and /dev/null differ diff --git a/ios/PhyWeb/Images.xcassets/LaunchImage.launchimage/Contents.json b/ios/PhyWeb/Images.xcassets/LaunchImage.launchimage/Contents.json deleted file mode 100644 index 3dd8aa5b..00000000 --- a/ios/PhyWeb/Images.xcassets/LaunchImage.launchimage/Contents.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "images" : [ - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "8.0", - "subtype" : "736h", - "scale" : "3x" - }, - { - "orientation" : "landscape", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "8.0", - "subtype" : "736h", - "scale" : "3x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "8.0", - "subtype" : "667h", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "subtype" : "retina4", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "1x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "1x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "2x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "scale" : "1x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "subtype" : "retina4", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "to-status-bar", - "scale" : "1x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "full-screen", - "scale" : "1x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "to-status-bar", - "scale" : "1x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "full-screen", - "scale" : "1x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "to-status-bar", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "ipad", - "extent" : "full-screen", - "scale" : "2x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "to-status-bar", - "scale" : "2x" - }, - { - "orientation" : "landscape", - "idiom" : "ipad", - "extent" : "full-screen", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/ios/PhyWeb/Info.plist b/ios/PhyWeb/Info.plist deleted file mode 100644 index efc7c901..00000000 Binary files a/ios/PhyWeb/Info.plist and /dev/null differ diff --git a/ios/PhyWeb/PhyWeb.entitlements b/ios/PhyWeb/PhyWeb.entitlements deleted file mode 100644 index bf1edc86..00000000 --- a/ios/PhyWeb/PhyWeb.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.physical-web.iosapp - - - diff --git a/ios/PhyWeb/Resources/37x-Checkmark.png b/ios/PhyWeb/Resources/37x-Checkmark.png deleted file mode 100644 index f330b70a..00000000 Binary files a/ios/PhyWeb/Resources/37x-Checkmark.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/37x-Checkmark@2x.png b/ios/PhyWeb/Resources/37x-Checkmark@2x.png deleted file mode 100644 index 2cb680b5..00000000 Binary files a/ios/PhyWeb/Resources/37x-Checkmark@2x.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/LaunchIcon.png b/ios/PhyWeb/Resources/LaunchIcon.png deleted file mode 100644 index 0d5260f5..00000000 Binary files a/ios/PhyWeb/Resources/LaunchIcon.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/LaunchScreen.xib b/ios/PhyWeb/Resources/LaunchScreen.xib deleted file mode 100644 index efa420a4..00000000 --- a/ios/PhyWeb/Resources/LaunchScreen.xib +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/PhyWeb/Resources/Progress1.png b/ios/PhyWeb/Resources/Progress1.png deleted file mode 100644 index 4b592faa..00000000 Binary files a/ios/PhyWeb/Resources/Progress1.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/Progress2.png b/ios/PhyWeb/Resources/Progress2.png deleted file mode 100644 index 0135e649..00000000 Binary files a/ios/PhyWeb/Resources/Progress2.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/Progress3.png b/ios/PhyWeb/Resources/Progress3.png deleted file mode 100644 index 3a41d0ff..00000000 Binary files a/ios/PhyWeb/Resources/Progress3.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/ScanError.png b/ios/PhyWeb/Resources/ScanError.png deleted file mode 100644 index f0ab5aaf..00000000 Binary files a/ios/PhyWeb/Resources/ScanError.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/gear.png b/ios/PhyWeb/Resources/gear.png deleted file mode 100644 index 699445e9..00000000 Binary files a/ios/PhyWeb/Resources/gear.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/gear@2x.png b/ios/PhyWeb/Resources/gear@2x.png deleted file mode 100644 index dc880223..00000000 Binary files a/ios/PhyWeb/Resources/gear@2x.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-100.png b/ios/PhyWeb/Resources/icon-100.png deleted file mode 100644 index 7b316d04..00000000 Binary files a/ios/PhyWeb/Resources/icon-100.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-114.png b/ios/PhyWeb/Resources/icon-114.png deleted file mode 100644 index fe0be176..00000000 Binary files a/ios/PhyWeb/Resources/icon-114.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-120.png b/ios/PhyWeb/Resources/icon-120.png deleted file mode 100644 index 4088d1ef..00000000 Binary files a/ios/PhyWeb/Resources/icon-120.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-144.png b/ios/PhyWeb/Resources/icon-144.png deleted file mode 100644 index e104eb0e..00000000 Binary files a/ios/PhyWeb/Resources/icon-144.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-152.png b/ios/PhyWeb/Resources/icon-152.png deleted file mode 100644 index a816f18b..00000000 Binary files a/ios/PhyWeb/Resources/icon-152.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-180.png b/ios/PhyWeb/Resources/icon-180.png deleted file mode 100644 index 6f4ab30a..00000000 Binary files a/ios/PhyWeb/Resources/icon-180.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-29.png b/ios/PhyWeb/Resources/icon-29.png deleted file mode 100644 index 7bf4b317..00000000 Binary files a/ios/PhyWeb/Resources/icon-29.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-40.png b/ios/PhyWeb/Resources/icon-40.png deleted file mode 100644 index 20442f38..00000000 Binary files a/ios/PhyWeb/Resources/icon-40.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-50.png b/ios/PhyWeb/Resources/icon-50.png deleted file mode 100644 index 90821f4d..00000000 Binary files a/ios/PhyWeb/Resources/icon-50.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-512.png b/ios/PhyWeb/Resources/icon-512.png deleted file mode 100644 index b48cf760..00000000 Binary files a/ios/PhyWeb/Resources/icon-512.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-57.png b/ios/PhyWeb/Resources/icon-57.png deleted file mode 100644 index 78659137..00000000 Binary files a/ios/PhyWeb/Resources/icon-57.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-58.png b/ios/PhyWeb/Resources/icon-58.png deleted file mode 100644 index f4df1718..00000000 Binary files a/ios/PhyWeb/Resources/icon-58.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-72.png b/ios/PhyWeb/Resources/icon-72.png deleted file mode 100644 index 80a895f3..00000000 Binary files a/ios/PhyWeb/Resources/icon-72.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-76.png b/ios/PhyWeb/Resources/icon-76.png deleted file mode 100644 index 50ae3f73..00000000 Binary files a/ios/PhyWeb/Resources/icon-76.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-80.png b/ios/PhyWeb/Resources/icon-80.png deleted file mode 100644 index 54ef07b3..00000000 Binary files a/ios/PhyWeb/Resources/icon-80.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/icon-87.png b/ios/PhyWeb/Resources/icon-87.png deleted file mode 100644 index e9294283..00000000 Binary files a/ios/PhyWeb/Resources/icon-87.png and /dev/null differ diff --git a/ios/PhyWeb/Resources/licenses.html b/ios/PhyWeb/Resources/licenses.html deleted file mode 100644 index 92d749d6..00000000 --- a/ios/PhyWeb/Resources/licenses.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - -

SDWebImage

- -

- Copyright (c) 2009 Olivier Poitrey <rs@dailymotion.com> -

- Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is furnished - to do so, subject to the following conditions: -

- -

- The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. -

- -

- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -

-

- -

MBProgressHUD

-

- Copyright (c) 2013 Matej Bukovinski -

- -

- Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: -

- -

- The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. -

- -

- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -

- -

SVPullToRefresh

-

- Copyright (C) 2012 Sam Vermette -

- -

- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -

- -

- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -

- -

- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -

- - diff --git a/ios/PhyWeb/UI/PWActivityIndicator.h b/ios/PhyWeb/UI/PWActivityIndicator.h deleted file mode 100644 index 6ab645e0..00000000 --- a/ios/PhyWeb/UI/PWActivityIndicator.h +++ /dev/null @@ -1,29 +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. - */ - -#import - -// This view shows a bluetooth scanning activity feedback. - -@interface PWActivityIndicator : UIView - -// Start animation. -- (void)start; - -// Stops animation. -- (void)stop; - -@end diff --git a/ios/PhyWeb/UI/PWActivityIndicator.m b/ios/PhyWeb/UI/PWActivityIndicator.m deleted file mode 100644 index 984dd49c..00000000 --- a/ios/PhyWeb/UI/PWActivityIndicator.m +++ /dev/null @@ -1,109 +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. - */ - -#import "PWActivityIndicator.h" - -@implementation PWActivityIndicator { - UIImageView *_defaultView; - UIImageView *_progress1View; - UIImageView *_progress2View; - UIImageView *_progress3View; - int _currentImage; - BOOL _started; -} - -- (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - _currentImage = 0; - _defaultView = [[UIImageView alloc] - initWithImage:[UIImage imageNamed:@"LaunchIcon.png"]]; - [self addSubview:_defaultView]; - _progress1View = - [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Progress1.png"]]; - [self addSubview:_progress1View]; - _progress2View = - [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Progress2.png"]]; - [self addSubview:_progress2View]; - _progress3View = - [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Progress3.png"]]; - [self addSubview:_progress3View]; - return self; -} - -- (void)layoutSubviews { - [_defaultView setFrame:[self bounds]]; - [_progress1View setFrame:[self bounds]]; - [_progress2View setFrame:[self bounds]]; - [_progress3View setFrame:[self bounds]]; -} - -- (void)start { - if (_started) { - return; - } - _started = YES; - [self _showNextImage]; -} - -- (void)_showNextImage { - _currentImage++; - _currentImage %= 4; - - CGFloat alphaDefault = 0; - CGFloat alpha1 = 0; - CGFloat alpha2 = 0; - CGFloat alpha3 = 0; - - switch (_currentImage) { - case 0: - alphaDefault = 1; - break; - case 1: - alpha1 = 1; - break; - case 2: - alpha2 = 1; - break; - case 3: - alpha3 = 1; - break; - } - - [UIView animateWithDuration:0.25 - animations:^{ - [_defaultView setAlpha:alphaDefault]; - [_progress1View setAlpha:alpha1]; - [_progress2View setAlpha:alpha2]; - [_progress3View setAlpha:alpha3]; - }]; - [self performSelector:@selector(_showNextImage) - withObject:nil - afterDelay:1.5]; -} - -- (void)stop { - _started = NO; - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - [UIView animateWithDuration:0.25 - animations:^{ - [_defaultView setAlpha:1.0]; - [_progress1View setAlpha:0.0]; - [_progress2View setAlpha:0.0]; - [_progress3View setAlpha:0.0]; - }]; -} - -@end diff --git a/ios/PhyWeb/UI/PWBeaconCell.h b/ios/PhyWeb/UI/PWBeaconCell.h deleted file mode 100644 index cceaae5a..00000000 --- a/ios/PhyWeb/UI/PWBeaconCell.h +++ /dev/null @@ -1,34 +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. - */ - -#import - -@class PWBeacon; - -@interface PWBeaconCell : UITableViewCell - -// Beacon to show. -@property(nonatomic, retain) PWBeacon *beacon; - -// Initializer. -- (instancetype)initWithStyle:(UITableViewCellStyle)style - reuseIdentifier:(NSString *)reuseIdentifier; - -// Returns cell height required to show the given beacon. -+ (CGFloat)heightForDevice:(PWBeacon *)beacon - tableView:(UITableView *)tableView; - -@end diff --git a/ios/PhyWeb/UI/PWBeaconCell.m b/ios/PhyWeb/UI/PWBeaconCell.m deleted file mode 100644 index 1d0b5c42..00000000 --- a/ios/PhyWeb/UI/PWBeaconCell.m +++ /dev/null @@ -1,270 +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. - */ - -#import "PWBeaconCell.h" - -#import "PWBeacon.h" -#import "PWSignalStrengthView.h" -#import - -#define FAVICON_WHITESPACE 1 - -#define TITLE_FONT_SIZE 18 -#define TITLE_FONT [UIFont systemFontOfSize:TITLE_FONT_SIZE] -#if TODAY_EXTENSION -#define TITLE_COLOR [UIColor whiteColor] -#else -#define TITLE_COLOR [UIColor colorWithRed:0.1529 green:0 blue:0.6510 alpha:1.0] -#endif - -#define URL_FONT_SIZE 14 -#define URL_FONT [UIFont systemFontOfSize:URL_FONT_SIZE] -#if TODAY_EXTENSION -#define URL_COLOR [UIColor colorWithWhite:0.85 alpha:1.0] -#else -#define URL_COLOR [UIColor colorWithRed:0 green:0.4235 blue:0.098 alpha:1.0] -#endif - -#define DESC_FONT_SIZE 14 -#define DESC_FONT [UIFont systemFontOfSize:URL_FONT_SIZE] -#if TODAY_EXTENSION -#define DESC_COLOR [UIColor whiteColor] -#else -#define DESC_COLOR [UIColor blackColor] -#endif - -#define FAVICON_SIZE 20 -#if TODAY_EXTENSION -#define MARGIN 50 -#else -#define MARGIN 20 -#endif -#define VERTICAL_MARGIN 10 -#define INNER_MARGIN 5 -#define URL_MARGIN 2 - -#define TITLE_MAX_HEIGHT 39 -#define URL_MAX_HEIGHT 17 -#define DESC_MAX_HEIGHT 51 - -typedef struct { - CGRect titleRect; - CGRect urlRect; - CGRect descriptionRect; -} CellDimension; - -@implementation PWBeaconCell { - UILabel *_titleLabel; - UILabel *_urlLabel; - UILabel *_descriptionLabel; - UIImageView *_faviconView; - PWSignalStrengthView *_strengthView; - PWBeacon *_beacon; -} - -@synthesize beacon = _beacon; - -- (instancetype)initWithStyle:(UITableViewCellStyle)style - reuseIdentifier:(NSString *)reuseIdentifier { - self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - - _strengthView = [[PWSignalStrengthView alloc] initWithFrame:CGRectZero]; - [_strengthView setHidden:YES]; - [self addSubview:_strengthView]; - _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [self addSubview:_titleLabel]; - _urlLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [self addSubview:_urlLabel]; - _descriptionLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [self addSubview:_descriptionLabel]; - _faviconView = [[UIImageView alloc] initWithFrame:CGRectZero]; - [_faviconView setContentMode:UIViewContentModeScaleAspectFit]; - [self addSubview:_faviconView]; - - [_titleLabel setFont:TITLE_FONT]; - [_titleLabel setTextColor:TITLE_COLOR]; - [_titleLabel setNumberOfLines:2]; - [_urlLabel setFont:URL_FONT]; - [_urlLabel setTextColor:URL_COLOR]; - [_descriptionLabel setFont:DESC_FONT]; - [_descriptionLabel setTextColor:DESC_COLOR]; - [_descriptionLabel setNumberOfLines:3]; - - [_titleLabel setLineBreakMode:NSLineBreakByTruncatingTail]; - [_urlLabel setLineBreakMode:NSLineBreakByTruncatingTail]; - [_descriptionLabel setLineBreakMode:NSLineBreakByTruncatingTail]; -#if TODAY_EXTENSION - [_faviconView setHidden:YES]; - [_descriptionLabel setHidden:YES]; - - UIView *selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero]; - [selectedBackgroundView - setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:0.07]]; - [self setSelectedBackgroundView:selectedBackgroundView]; - -#endif - - return self; -} - -- (void)prepareForReuse { - [_faviconView setImage:nil]; -} - -- (void)layoutSubviews { -#if TODAY_EXTENSION - [[self selectedBackgroundView] setFrame:[self bounds]]; -#endif - - CellDimension dimension; - computeLayout(_beacon, [self bounds].size.width, &dimension); -#if !TODAY_EXTENSION - if ([_beacon iconURL] != nil) { - [_faviconView setHidden:NO]; - } else { - if (FAVICON_WHITESPACE) { - [_faviconView setHidden:NO]; - } else { - [_faviconView setHidden:YES]; - } - } -#endif - - [_titleLabel setFrame:dimension.titleRect]; - [_urlLabel setFrame:dimension.urlRect]; - [_descriptionLabel setFrame:dimension.descriptionRect]; - - CGRect frame; - CGRect bounds = [self bounds]; - frame = CGRectMake(bounds.size.width - MARGIN - 15, - VERTICAL_MARGIN + FAVICON_SIZE + 10, 15, 15); - [_strengthView setFrame:frame]; - bounds = [self bounds]; - frame = CGRectMake(bounds.size.width - MARGIN - FAVICON_SIZE, VERTICAL_MARGIN, - FAVICON_SIZE, FAVICON_SIZE); - [_faviconView setFrame:frame]; -} - -- (void)setBeacon:(PWBeacon *)beacon { - _beacon = beacon; - - [_titleLabel setText:[[self beacon] title]]; - [_urlLabel setText:[[[self beacon] displayURL] absoluteString]]; - [_descriptionLabel setText:snippetForBeacon([self beacon])]; -#if !TODAY_EXTENSION - if ([[self beacon] iconURL] == nil) { - [_faviconView setImage:nil]; - } else { - [_faviconView sd_setImageWithURL:[[self beacon] iconURL]]; - } -#endif - [_strengthView setHidden:![[NSUserDefaults standardUserDefaults] - boolForKey:@"DebugMode"]]; - - NSInteger rssi = [[[self beacon] uriBeacon] RSSI]; - if (rssi == 127) { - rssi = -100; - } - if (rssi < -100) { - rssi = -100; - } - if (rssi > -50) { - rssi = -50; - } - // Compute quality in percent based on RSSI value. - NSInteger quality = 2 * (rssi + 100); - [_strengthView setQuality:quality]; - - [self setNeedsLayout]; -} - -#define FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE 41 - -static double distanceFromRSSI(int txPower, double rssi) { - int pathLoss = txPower - rssi; - return pow(10.0, (pathLoss - FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE) / 20.0); -} - -static NSString *snippetForBeacon(PWBeacon *beacon) { - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DebugMode"]) { - return [NSString - stringWithFormat: - @"[discovery:%g request:%g rssi:%i tx:%i dist:%.2g rank: %.2g] %@", - [beacon discoveryDelay], [beacon requestDelay], - (int)[[beacon uriBeacon] RSSI], - (int)[[beacon uriBeacon] txPowerLevel], - distanceFromRSSI([[beacon uriBeacon] txPowerLevel], - [[beacon uriBeacon] RSSI]), - [beacon rank], [beacon snippet] != nil ? [beacon snippet] : @""]; - } else { - return [beacon snippet]; - } -} - -static void computeLayout(PWBeacon *beacon, CGFloat containerWidth, - CellDimension *result) { - CGRect titleRect; - CGRect urlRect; - CGRect descriptionRect; - - NSMutableDictionary *attr = [[NSMutableDictionary alloc] init]; - CGSize size; - - [attr setObject:TITLE_FONT forKey:NSFontAttributeName]; - size = CGSizeMake(containerWidth - (FAVICON_SIZE + MARGIN + MARGIN), - TITLE_MAX_HEIGHT); - titleRect = - [[beacon title] boundingRectWithSize:size - options:NSStringDrawingUsesLineFragmentOrigin - attributes:attr - context:nil]; - titleRect.origin = CGPointMake(MARGIN, VERTICAL_MARGIN); - titleRect = CGRectIntegral(titleRect); - - urlRect.origin = CGPointMake(MARGIN, CGRectGetMaxY(titleRect) + INNER_MARGIN); - urlRect.size = CGSizeMake(containerWidth - (FAVICON_SIZE + MARGIN + MARGIN), - URL_MAX_HEIGHT); - - [attr setObject:DESC_FONT forKey:NSFontAttributeName]; - size = CGSizeMake(containerWidth - (MARGIN + MARGIN), DESC_MAX_HEIGHT); - NSString *snippet = snippetForBeacon(beacon); - if (snippet == nil) { - descriptionRect.size = CGSizeZero; - descriptionRect.origin = CGPointMake(MARGIN, CGRectGetMaxY(urlRect)); - } else { - descriptionRect = - [snippet boundingRectWithSize:size - options:NSStringDrawingUsesLineFragmentOrigin - attributes:attr - context:nil]; - descriptionRect.origin = - CGPointMake(MARGIN, CGRectGetMaxY(urlRect) + INNER_MARGIN); - descriptionRect = CGRectIntegral(descriptionRect); - } - - result->titleRect = titleRect; - result->urlRect = urlRect; - result->descriptionRect = descriptionRect; -} - -+ (CGFloat)heightForDevice:(PWBeacon *)device - tableView:(UITableView *)tableView { - CellDimension dimension; - computeLayout(device, [tableView bounds].size.width, &dimension); - return CGRectGetMaxY(dimension.descriptionRect) + VERTICAL_MARGIN; -} - -@end diff --git a/ios/PhyWeb/UI/PWBeaconChartTableViewCell.h b/ios/PhyWeb/UI/PWBeaconChartTableViewCell.h deleted file mode 100644 index afdcf97e..00000000 --- a/ios/PhyWeb/UI/PWBeaconChartTableViewCell.h +++ /dev/null @@ -1,23 +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. - */ - -#import - -@interface PWBeaconChartTableViewCell : UITableViewCell - -@property(nonatomic, retain, readonly) UILabel* rssiLabel; - -@end diff --git a/ios/PhyWeb/UI/PWBeaconChartTableViewCell.m b/ios/PhyWeb/UI/PWBeaconChartTableViewCell.m deleted file mode 100644 index d8166047..00000000 --- a/ios/PhyWeb/UI/PWBeaconChartTableViewCell.m +++ /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. - */ - -#import "PWBeaconChartTableViewCell.h" - -@implementation PWBeaconChartTableViewCell { - UILabel *_rssiLabel; -} - -@synthesize rssiLabel = _rssiLabel; - -- (id)initWithStyle:(UITableViewCellStyle)style - reuseIdentifier:(NSString *)reuseIdentifier { - self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; - - _rssiLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [_rssiLabel setTextAlignment:NSTextAlignmentRight]; - [[self contentView] addSubview:_rssiLabel]; - - return self; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - CGRect frame = CGRectMake(10, 10, [self bounds].size.width - 100, 40); - [[self textLabel] setFrame:frame]; - - frame = CGRectMake([self bounds].size.width - 90, 10, 80, 40); - [_rssiLabel setFrame:frame]; -} - -@end diff --git a/ios/PhyWeb/UI/PWBeaconsViewController.h b/ios/PhyWeb/UI/PWBeaconsViewController.h deleted file mode 100644 index fbd3c82e..00000000 --- a/ios/PhyWeb/UI/PWBeaconsViewController.h +++ /dev/null @@ -1,26 +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. - */ - -#import - -// This part of the UI will show a list of beacons. -@interface PWBeaconsViewController : UIViewController - -- (void)updateBeaconsNow; - -- (void)disablePlaceholder; - -@end diff --git a/ios/PhyWeb/UI/PWBeaconsViewController.m b/ios/PhyWeb/UI/PWBeaconsViewController.m deleted file mode 100644 index 51f2e440..00000000 --- a/ios/PhyWeb/UI/PWBeaconsViewController.m +++ /dev/null @@ -1,534 +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. - */ - -#import "PWBeaconsViewController.h" - -#import -#import -#import "SVPullToRefresh.h" - -#import "PWBeaconManager.h" -#import "PWBeacon.h" -#import "PWBeaconCell.h" -#import "PWGradientView.h" -#import "PWMetadataRequest.h" -#import "PWPlaceholderView.h" -#import "PWSettingsViewController.h" -#import "PWSimpleWebViewController.h" -#import "PWChartViewController.h" - -@interface PWBeaconsViewController ()< - UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, - CBCentralManagerDelegate, PWMetadataRequestDelegate, - PWSimpleWebViewControllerDelegate> - -@end - -@implementation PWBeaconsViewController { - UITableView *_tableView; - BOOL _canShowPlaceholder; - BOOL _shouldShowLogs; - BOOL _showPlaceholder; - BOOL _firstUpdate; - NSMutableArray *_beacons; - BOOL _scheduledUpdated; - PWPlaceholderView *_placeholderView; - PWGradientView *_gradientView; - CBCentralManager *_centralManager; - BOOL _showDemoBeacons; - UIButton *_showDemoBeaconsButton; - UIActivityIndicatorView *_activityView; - PWMetadataRequest *_demoBeaconsRequest; - NSArray *_demoBeacons; - BOOL _refreshing; -} - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil - bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - _firstUpdate = YES; - [[PWBeaconManager sharedManager] registerChangeBlock:^{ - [self _updateViewAfterDelay]; - }]; - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(_applicationDidResignActive) - name:UIApplicationWillResignActiveNotification - object:[UIApplication sharedApplication]]; - - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGRect bounds = [[self view] bounds]; - _tableView = - [[UITableView alloc] initWithFrame:bounds style:UITableViewStylePlain]; - [_tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth]; - [_tableView setDelegate:self]; - [_tableView setDataSource:self]; - [_tableView setRowHeight:100]; - [_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; - [_tableView setScrollsToTop:YES]; - PWBeaconsViewController *__weak weakSelf = self; - [_tableView addPullToRefreshWithActionHandler:^{ - [weakSelf _performPullToRefresh]; - }]; - [self _updateLayoutForSize:bounds.size]; - [[self view] addSubview:_tableView]; - - _placeholderView = [[PWPlaceholderView alloc] initWithFrame:CGRectZero]; - [self disablePlaceholder]; - - _showDemoBeaconsButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - [_showDemoBeaconsButton setTitle:@"Show Example Beacons" - forState:UIControlStateNormal]; - [_showDemoBeaconsButton - setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:.9]]; - [_showDemoBeaconsButton - setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleRightMargin | - UIViewAutoresizingFlexibleTopMargin]; - [_showDemoBeaconsButton addTarget:self - action:@selector(_showDemoBeaconsButtonPressed) - forControlEvents:UIControlEventTouchUpInside]; - [_showDemoBeaconsButton setAlpha:0.0]; - [_placeholderView addSubview:_showDemoBeaconsButton]; - _activityView = [[UIActivityIndicatorView alloc] - initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - [_placeholderView addSubview:_activityView]; - - CGRect frame = bounds; - frame.size.height = 25; - _gradientView = [[PWGradientView alloc] initWithFrame:frame]; - [_gradientView setAlpha:0.0]; - [[self view] addSubview:_gradientView]; - - UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - [[button layer] - setBorderColor:[[UIColor colorWithWhite:0.95 alpha:1.0] CGColor]]; - [[button layer] setBorderWidth:1.0]; - [[button layer] setCornerRadius:5.0]; - [button setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleTopMargin]; - [button setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:.9]]; - [button setImage:[UIImage imageNamed:@"gear"] forState:UIControlStateNormal]; - [button setFrame:CGRectMake(bounds.size.width - 45, bounds.size.height - 45, - 40, 40)]; - [button addTarget:self - action:@selector(_settingsPressed) - forControlEvents:UIControlEventTouchUpInside]; - [[self view] addSubview:button]; - - _centralManager = - [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; - - [self _reloadData]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [self _showGettingStartedDialog]; -} - -- (void)_showGettingStartedDialog { - if ([[NSUserDefaults standardUserDefaults] - boolForKey:@"GettingStartedDialogShown"]) { - return; - } - - PWSimpleWebViewController *controller = [[PWSimpleWebViewController alloc] - initWithURL:[NSURL URLWithString:@"http://google.github.io/" - @"physical-web/mobile/ios/" @"getting-started.html"]]; - [controller setTitle:@"Getting Started"]; - [controller setProceedButtonVisible:YES]; - [controller setDelegate:self]; - - UINavigationController *navigationController = - [[UINavigationController alloc] initWithRootViewController:controller]; - [self presentViewController:navigationController animated:YES completion:nil]; -} - -- (void)simpleWebViewControllerProceedPressed: - (PWSimpleWebViewController *)controller { - [[NSUserDefaults standardUserDefaults] setBool:YES - forKey:@"GettingStartedDialogShown"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)viewWillTransitionToSize:(CGSize)size - withTransitionCoordinator: - (id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [self _updateLayoutForSize:size]; -} - -- (void)_updateLayoutForSize:(CGSize)size { - if (size.width > size.height) { - [_tableView setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)]; - } else { - [_tableView setContentInset:UIEdgeInsetsMake(20, 0, 0, 0)]; - } -} - -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - [self _updatedPlaceholderViewState]; -} - -- (void)_applicationDidResignActive { - if (!_showDemoBeacons) { - return; - } - - // Hide demo beacons - _showDemoBeacons = NO; - _demoBeacons = nil; - [self _updateBeaconsNow]; -} - -- (void)_performPullToRefresh { - if (_refreshing) { - return; - } - - if ([[PWBeaconManager sharedManager] isStarted]) { - [[PWBeaconManager sharedManager] stop]; - } - [self disablePlaceholder]; - [[PWBeaconManager sharedManager] resetBeacons]; - [[PWBeaconManager sharedManager] start]; - [self updateBeaconsNow]; - - _refreshing = YES; - [self performSelector:@selector(_performPullToRefreshDone) - withObject:nil - afterDelay:2.0]; -} - -- (void)_performPullToRefreshDone { - if (!_refreshing) { - return; - } - [NSObject - cancelPreviousPerformRequestsWithTarget:self - selector:@selector( - _performPullToRefreshDone) - object:nil]; - [[_tableView pullToRefreshView] stopAnimating]; - _refreshing = NO; -} - -- (void)_showDemoBeaconsButtonPressed { - if (_showDemoBeacons) { - return; - } - _showDemoBeacons = YES; - _demoBeacons = nil; - _demoBeaconsRequest = [[PWMetadataRequest alloc] init]; - [_demoBeaconsRequest setDemo:YES]; - [_demoBeaconsRequest setDelegate:self]; - [_demoBeaconsRequest start]; - - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; - [_activityView startAnimating]; - - [self _reloadData]; -} - -- (void)_updatedPlaceholderViewState { - BOOL enabled = ([_centralManager state] == CBCentralManagerStatePoweredOn); - [_placeholderView setBluetoothEnabled:enabled]; - if (enabled) { - [_placeholderView setLabel:@"No beacons detected"]; - } else { - [_placeholderView setLabel:@"Please turn on bluetooth in order to start " - @"scanning for beacons."]; - } -} - -// Settings button handler. -- (void)_settingsPressed { - PWSettingsViewController *settingsViewController = - [[PWSettingsViewController alloc] initWithNibName:nil bundle:nil]; - UINavigationController *navigationController = [[UINavigationController alloc] - initWithRootViewController:settingsViewController]; - [self presentViewController:navigationController animated:YES completion:nil]; -} - -- (void)_updateViewAfterDelay { - if (_scheduledUpdated) { - return; - } - - _scheduledUpdated = YES; - // The first update will be scheduled after 1 sec, the subsequent updates will - // be scheduled after 5 sec. If the list was empty, an update will also be - // scheduled after 1 sec. - NSTimeInterval delay = 5; - if (_firstUpdate || [_beacons count] == 0 || - [[PWBeaconManager sharedManager] isStableMode]) { - delay = 1; - if ([[PWBeaconManager sharedManager] isStableMode] && _firstUpdate) { - delay = 1.5; - } - } - [self performSelector:@selector(_updateBeaconsNow) - withObject:nil - afterDelay:delay]; - _firstUpdate = NO; -} - -- (void)_updateBeaconsNow { - [self _performPullToRefreshDone]; - - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_updateBeaconsNow) - object:nil]; - _scheduledUpdated = NO; - BOOL firstUpdate = ([_beacons count] == 0); - _beacons = [[[PWBeaconManager sharedManager] beacons] mutableCopy]; - [_beacons addObjectsFromArray:_demoBeacons]; - if (firstUpdate) { - for (PWBeacon *beacon in _beacons) { - [beacon setSortByRegion:YES]; - [beacon setRegion:[[beacon uriBeacon] region]]; - } - } else { - NSDate *date = [NSDate date]; - for (PWBeacon *beacon in _beacons) { - if (![beacon sortByRegion]) { - if ([beacon date] == nil) { - [beacon setDate:date]; - } - } - } - } - [self _sort]; - [self _reloadData]; - - [[PWBeaconManager sharedManager] serializeBeacons:_beacons]; -} - -// Sort results by RSSI value. -- (void)_sort { - for (PWBeacon *beacon in _beacons) { - if (![beacon hasRank]) { - [beacon setHasRank:YES]; - [beacon setRank:1000]; - } - } - [_beacons sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - PWBeacon *beacon1 = obj1; - PWBeacon *beacon2 = obj2; - double diff = [beacon1 rank] - [beacon2 rank]; - if (diff > 0) { - return NSOrderedDescending; - } else if (diff < 0) { - return NSOrderedAscending; - } else { - NSComparisonResult result = - [[beacon1 title] caseInsensitiveCompare:[beacon2 title]]; - if (result != NSOrderedSame) { - return result; - } - return [[[beacon1 URL] absoluteString] - caseInsensitiveCompare:[[beacon2 URL] absoluteString]]; - } - }]; -} - -- (void)_reloadData { - if ([_beacons count] == 0) { - _showPlaceholder = YES; - [_placeholderView start]; - } else { - _showPlaceholder = NO; - [_placeholderView stop]; - } - [_tableView reloadData]; -} - -- (void)_enablePlaceholder { - _canShowPlaceholder = YES; - [_placeholderView setShowLabel:YES]; - [UIView animateWithDuration:0.25 - animations:^{ - [_showDemoBeaconsButton setAlpha:1.0]; - }]; - [self _reloadData]; - - CGRect bounds = [_placeholderView bounds]; - CGRect frame = CGRectMake((bounds.size.width - 200) / 2, - bounds.size.height - 100, 200, 40); - frame = CGRectIntegral(frame); - [_showDemoBeaconsButton setFrame:frame]; - [_activityView - setCenter:CGPointMake(bounds.size.width / 2, bounds.size.height - 50)]; -} - -- (void)disablePlaceholder { - _canShowPlaceholder = NO; - [_placeholderView setShowLabel:NO animated:NO]; - [_showDemoBeaconsButton setAlpha:0.0]; - - [NSObject - cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_enablePlaceholder) - object:nil]; - [self performSelector:@selector(_enablePlaceholder) - withObject:nil - afterDelay:2.5]; -} - -- (BOOL)canBecomeFirstResponder { - return YES; -} - -#pragma mark UITableView data source - -- (CGFloat)tableView:(UITableView *)tableView - heightForHeaderInSection:(NSInteger)section { - return _showPlaceholder ? [UIScreen mainScreen].bounds.size.height : 0; -} - -- (UIView *)tableView:(UITableView *)tableView - viewForHeaderInSection:(NSInteger)section { - return _showPlaceholder ? _placeholderView : nil; -} - -- (NSInteger)tableView:(UITableView *)tableView - numberOfRowsInSection:(NSInteger)section { - return [_beacons count]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView - cellForRowAtIndexPath:(NSIndexPath *)indexPath { - PWBeaconCell *cell = [tableView dequeueReusableCellWithIdentifier:@"device"]; - if (cell == nil) { - cell = [[PWBeaconCell alloc] initWithStyle:UITableViewCellStyleSubtitle - reuseIdentifier:@"device"]; - } - PWBeacon *beacon = [_beacons objectAtIndex:[indexPath row]]; - [cell setBeacon:beacon]; - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView - heightForRowAtIndexPath:(NSIndexPath *)indexPath { - PWBeacon *device = [_beacons objectAtIndex:[indexPath row]]; - return [PWBeaconCell heightForDevice:device tableView:_tableView]; -} - -- (void)tableView:(UITableView *)tableView - didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DebugMode"]) { - PWChartViewController *detailController = - [[PWChartViewController alloc] initWithNibName:nil bundle:nil]; - PWBeacon *beacon = [_beacons objectAtIndex:[indexPath row]]; - NSURL *url = [beacon displayURL]; - [detailController setURL:url]; - UINavigationController *navigationController = - [[UINavigationController alloc] - initWithRootViewController:detailController]; - [self presentViewController:navigationController - animated:YES - completion:nil]; - [_tableView deselectRowAtIndexPath:indexPath animated:YES]; - } else { - PWBeacon *beacon = [_beacons objectAtIndex:[indexPath row]]; - NSURL *url = [[beacon uriBeacon] URI]; - NSString *unescaped = [url absoluteString]; - NSString *escapedString = - [unescaped stringByAddingPercentEncodingWithAllowedCharacters: - [NSCharacterSet URLHostAllowedCharacterSet]]; - NSString *goURLString = - [NSString stringWithFormat:@"http://%@/go?url=%@", - [PWMetadataRequest hostname], escapedString]; - NSURL *goURL = [NSURL URLWithString:goURLString]; - [[UIApplication sharedApplication] openURL:goURL]; - - [_tableView deselectRowAtIndexPath:indexPath animated:YES]; - } -} - -#pragma mark scroll view delegate method - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - CGFloat alphaValue = [scrollView contentOffset].y > 0 ? 1.0 : 0.0; - if ([_gradientView alpha] != alphaValue) { - [UIView animateWithDuration:0.25 - animations:^{ - [_gradientView setAlpha:alphaValue]; - }]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView - withVelocity:(CGPoint)velocity - targetContentOffset:(inout CGPoint *)targetContentOffset { -#if 0 - if ([scrollView contentOffset].y < -150) { - [[PWBeaconManager sharedManager] - setStableMode:![[PWBeaconManager sharedManager] isStableMode]]; - - MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - [hud setMode:MBProgressHUDModeText]; - [hud setLabelText:[[PWBeaconManager sharedManager] isStableMode] - ? @"Stable Mode" - : @"Dynamic Mode"]; - [hud hide:YES afterDelay:1.5]; - } -#endif -} - -#pragma mark metadata request response - -- (void)metadataRequest_done:(PWMetadataRequest *)request { - if ([request error] != nil) { - UIAlertView *alertView = [[UIAlertView alloc] - initWithTitle:@"Could not connect to the server" - message:@"Please check whether your internet connection is " - @"working properly." - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alertView show]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - [_activityView stopAnimating]; - _showDemoBeacons = NO; - return; - } - - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - [_activityView stopAnimating]; - _demoBeacons = [_demoBeaconsRequest results]; - _demoBeaconsRequest = nil; - [self _updateBeaconsNow]; -} - -- (void)updateBeaconsNow { - [self _updateBeaconsNow]; -} - -@end diff --git a/ios/PhyWeb/UI/PWChartViewController.h b/ios/PhyWeb/UI/PWChartViewController.h deleted file mode 100644 index 38fbca1b..00000000 --- a/ios/PhyWeb/UI/PWChartViewController.h +++ /dev/null @@ -1,25 +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. - */ - -#import - -@class PWBeacon; - -@interface PWChartViewController : UIViewController - -@property (nonatomic, copy) NSURL * URL; - -@end diff --git a/ios/PhyWeb/UI/PWChartViewController.m b/ios/PhyWeb/UI/PWChartViewController.m deleted file mode 100644 index 507bb282..00000000 --- a/ios/PhyWeb/UI/PWChartViewController.m +++ /dev/null @@ -1,359 +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. - */ - -#import "PWChartViewController.h" - -#import "JBLineChartView.h" -#import "PWBeacon.h" -#import "PWBeaconManager.h" -#import "PWBeaconChartTableViewCell.h" - -@interface PWChartViewController () - -@end - -@implementation PWChartViewController { - JBLineChartView *_chartView; - int _beaconsCount; - int _maxTime; - CGFloat **_rssiValues; - CGFloat **_distanceValues; - UITableView *_tableView; - NSInteger _selectedHorizontalIndex; - NSMutableArray *_urls; - BOOL _showRSSI; - NSURL *_url; -} - -@synthesize URL = _url; - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil - bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - - UIBarButtonItem *doneButton = - [[UIBarButtonItem alloc] initWithTitle:@"Done" - style:UIBarButtonItemStylePlain - target:self - action:@selector(_done:)]; - [[self navigationItem] setLeftBarButtonItem:doneButton]; - UIBarButtonItem *toggleButton = - [[UIBarButtonItem alloc] initWithTitle:@"Toggle" - style:UIBarButtonItemStylePlain - target:self - action:@selector(_toggle:)]; - [[self navigationItem] setRightBarButtonItem:toggleButton]; - [self setTitle:@"Distance"]; - _selectedHorizontalIndex = -1; - - return self; -} - -- (void)_done:(id)sender { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)_toggle:(id)sender { - _showRSSI = !_showRSSI; - if (_showRSSI) { - [_chartView setMinimumValue:128]; - [_chartView setMaximumValue:256]; - [self setTitle:@"RSSI"]; - } else { - [_chartView setMinimumValue:0.0]; - [_chartView setMaximumValue:30.0]; - [self setTitle:@"Distance"]; - } - [_chartView reloadData]; - [_tableView reloadData]; -} - -#define FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE 41 - -static double distanceFromRSSI(int txPower, double rssi) { - int pathLoss = txPower - rssi; - return pow(10.0, (pathLoss - FREE_SPACE_PATH_LOSS_CONSTANT_FOR_BLE) / 20.0); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGRect frame = [[self view] bounds]; - frame.size.height = frame.size.height / 2; - _chartView = [[JBLineChartView alloc] initWithFrame:frame]; - [_chartView setMinimumValue:0.0]; - [_chartView setMaximumValue:30.0]; - [_chartView setDelegate:self]; - [_chartView setDataSource:self]; - [_chartView setBackgroundColor:[UIColor colorWithWhite:0.9 alpha:1.0]]; - [_chartView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth]; - [_chartView setShowsLineSelection:NO]; - [[self view] addSubview:_chartView]; - - UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] - initWithTarget:self - action:@selector(_panGestureRecognized:)]; - [panGesture setCancelsTouchesInView:NO]; - [_chartView addGestureRecognizer:panGesture]; - - frame = [[self view] bounds]; - frame.origin.y = frame.size.height / 2; - frame.size.height = frame.size.height / 2; - _tableView = [[UITableView alloc] initWithFrame:frame]; - [_tableView setDataSource:self]; - [_tableView setDelegate:self]; - [_tableView setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | - UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth]; - [[self view] addSubview:_tableView]; - - [self _periodicReloadData]; -} - -- (void)_panGestureRecognized:(UIPanGestureRecognizer *)gestureRecognizer { - [NSObject - cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_periodicReloadData) - object:nil]; - if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { - [self _periodicReloadData]; - } -} - -- (void)_periodicReloadData { - [self _reloadData]; - [self performSelector:@selector(_periodicReloadData) - withObject:nil - afterDelay:1.0]; -} - -- (void)_reloadData { - _urls = [[NSMutableArray alloc] init]; - for (PWBeacon *beacon in [[PWBeaconManager sharedManager] beacons]) { - [_urls addObject:[beacon displayURL]]; - } - - NSTimeInterval startTime = [[PWBeaconManager sharedManager] startTime]; - NSTimeInterval maxTime = startTime; - for (PWBeacon *beacon in [[PWBeaconManager sharedManager] beacons]) { - NSArray *lastValue = [[beacon rssiHistory] lastObject]; - if (lastValue == nil) { - continue; - } - // NSNumber * nbValue = [lastValue objectAtIndex:0]; - NSNumber *nbTimestamp = [lastValue objectAtIndex:1]; - if ([nbTimestamp doubleValue] > maxTime) { - maxTime = [nbTimestamp doubleValue]; - } - } - _beaconsCount = (int)[[[PWBeaconManager sharedManager] beacons] count] + 2; - _maxTime = ceil(maxTime - startTime) + 1; - _rssiValues = calloc(_beaconsCount, sizeof(CGFloat *)); - _distanceValues = calloc(_beaconsCount, sizeof(CGFloat *)); - for (int i = 0; i < _beaconsCount; i++) { - _rssiValues[i] = calloc(_maxTime, sizeof(CGFloat)); - _distanceValues[i] = calloc(_maxTime, sizeof(CGFloat)); - for (int k = 0; k < _maxTime; k++) { - _rssiValues[i][k] = NAN; - _distanceValues[i][k] = NAN; - } - } - - for (int i = 0; i < _beaconsCount; i++) { - if (i < 2) { - continue; - } - PWBeacon *beacon = - [[[PWBeaconManager sharedManager] beacons] objectAtIndex:i - 2]; - for (NSArray *value in [beacon rssiHistory]) { - NSNumber *nbValue = [value objectAtIndex:0]; - NSNumber *nbTimestamp = [value objectAtIndex:1]; - int time = round([nbTimestamp doubleValue]) - startTime; - _rssiValues[i][time] = - [[beacon uriBeacon] txPowerLevel] - [nbValue intValue]; - _distanceValues[i][time] = distanceFromRSSI( - [[beacon uriBeacon] txPowerLevel], [nbValue intValue]); - } - } - for (int i = 0; i < _beaconsCount; i++) { - CGFloat currentRssi = NAN; - CGFloat currentDistance = NAN; - if (i == 0) { - currentDistance = 2; - currentRssi = 37; - } else if (i == 1) { - currentDistance = 10; - currentRssi = 61; - } - for (int k = 0; k < _maxTime; k++) { - if (isnan(_rssiValues[i][k])) { - _rssiValues[i][k] = currentRssi; - } else { - currentRssi = _rssiValues[i][k]; - } - if (isnan(_distanceValues[i][k])) { - _distanceValues[i][k] = currentDistance; - } else { - currentDistance = _distanceValues[i][k]; - } - } - } - - [_tableView reloadData]; - [_chartView reloadData]; -} - -- (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView { - return _beaconsCount; -} - -- (NSUInteger)lineChartView:(JBLineChartView *)lineChartView - numberOfVerticalValuesAtLineIndex:(NSUInteger)lineIndex { - return 20; -} - -- (CGFloat)lineChartView:(JBLineChartView *)lineChartView - verticalValueForHorizontalIndex:(NSUInteger)horizontalIndex - atLineIndex:(NSUInteger)lineIndex { - if (_showRSSI) { - if (horizontalIndex >= _maxTime) { - return NAN; - } else { - if (_maxTime >= 20) { - return _rssiValues[lineIndex][horizontalIndex + _maxTime - 20] + 128; - } else { - return _rssiValues[lineIndex][horizontalIndex] + 128; - } - } - } else { - if (horizontalIndex >= _maxTime) { - return NAN; - } else { - if (_maxTime >= 20) { - return _distanceValues[lineIndex][horizontalIndex + _maxTime - 20]; - } else { - return _distanceValues[lineIndex][horizontalIndex]; - } - } - } -} - -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView - colorForLineAtLineIndex:(NSUInteger)lineIndex { - static NSArray *colors = nil; - if (colors == nil) { - colors = @[ - [UIColor brownColor], - [UIColor blueColor], - [UIColor redColor], - [UIColor greenColor], - [UIColor grayColor], - [UIColor orangeColor], - [UIColor purpleColor] - ]; - } - return colors[lineIndex % [colors count]]; -} - -- (CGFloat)lineChartView:(JBLineChartView *)lineChartView - widthForLineAtLineIndex:(NSUInteger)lineIndex { - if (lineIndex < 2) { - return 1.0; - } - if ([_urls[lineIndex - 2] isEqual:_url]) { - return 3.0; - } else { - return 1.0; - } -} - -- (JBLineChartViewLineStyle)lineChartView:(JBLineChartView *)lineChartView - lineStyleForLineAtLineIndex:(NSUInteger)lineIndex { - return lineIndex >= 2 ? JBLineChartViewLineStyleSolid - : JBLineChartViewLineStyleDashed; -} - -- (void)lineChartView:(JBLineChartView *)lineChartView - didSelectLineAtIndex:(NSUInteger)lineIndex - horizontalIndex:(NSUInteger)horizontalIndex - touchPoint:(CGPoint)touchPoint { - if (horizontalIndex >= _maxTime) { - } else { - if (_maxTime >= 20) { - _selectedHorizontalIndex = horizontalIndex + _maxTime - 20; - } else { - _selectedHorizontalIndex = horizontalIndex; - } - [_tableView reloadData]; - } -} - -- (NSInteger)tableView:(UITableView *)tableView - numberOfRowsInSection:(NSInteger)section { - return _beaconsCount - 2; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView - cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSArray *colors = nil; - if (colors == nil) { - colors = @[ - [UIColor brownColor], - [UIColor blueColor], - [UIColor redColor], - [UIColor greenColor], - [UIColor grayColor], - [UIColor orangeColor], - [UIColor purpleColor] - ]; - } - - NSInteger row = [indexPath row]; - PWBeaconChartTableViewCell *cell = (PWBeaconChartTableViewCell *) - [_tableView dequeueReusableCellWithIdentifier:@"Beacon"]; - if (cell == nil) { - cell = [[PWBeaconChartTableViewCell alloc] - initWithStyle:UITableViewCellStyleDefault - reuseIdentifier:@"Beacon"]; - } - [[cell textLabel] - setText:[NSString stringWithFormat:@"%@", [_urls objectAtIndex:row]]]; - if (_selectedHorizontalIndex != -1) { - if (_showRSSI) { - [[cell rssiLabel] - setText:[NSString - stringWithFormat:@"%5.2g", - _rssiValues[row + 2] - [_selectedHorizontalIndex]]]; - } else { - [[cell rssiLabel] - setText:[NSString - stringWithFormat: - @"%5.2g", - _distanceValues[row + 2][_selectedHorizontalIndex]]]; - } - } else { - [[cell rssiLabel] setText:@""]; - } - [[cell textLabel] setTextColor:colors[(row + 2) % [colors count]]]; - - return cell; -} - -@end diff --git a/ios/PhyWeb/UI/PWConfigureViewController.h b/ios/PhyWeb/UI/PWConfigureViewController.h deleted file mode 100644 index 7186b3f4..00000000 --- a/ios/PhyWeb/UI/PWConfigureViewController.h +++ /dev/null @@ -1,28 +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. - */ - -#import - -#import "UriBeacon.h" - -// This part of the UI will show a configuration panel for a configurable -// beacon. It will let the user set a URL for it. -@interface PWConfigureViewController : UIViewController - -// Beacon to configure. -@property(nonatomic, retain) UBConfigurableUriBeacon *beacon; - -@end diff --git a/ios/PhyWeb/UI/PWConfigureViewController.m b/ios/PhyWeb/UI/PWConfigureViewController.m deleted file mode 100644 index 8fac4b28..00000000 --- a/ios/PhyWeb/UI/PWConfigureViewController.m +++ /dev/null @@ -1,300 +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. - */ - -#import "PWConfigureViewController.h" - -#import "UriBeacon.h" -#import - -#import "PWURLShortener.h" - -#define DEFAULT_TX_POWER_LEVEL -22 - -@interface PWConfigureViewController () - -@end - -@implementation PWConfigureViewController { - UITextField *_textField; - BOOL _connected; - MBProgressHUD *_hud; - NSTimeInterval _delayStartTime; - UILabel *_shortenerLabel; - NSURL *_resultURL; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - [[self view] setBackgroundColor:[UIColor whiteColor]]; - - CGRect bounds = [[self view] bounds]; - UILabel *label = [[UILabel alloc] - initWithFrame:CGRectMake(10, 74, bounds.size.width - 20, 0)]; - [label setText:@"Enter URI:"]; - [label sizeToFit]; - [[self view] addSubview:label]; - - _textField = [[UITextField alloc] - initWithFrame:CGRectMake(10, CGRectGetMaxY([label frame]) + 10, - bounds.size.width - 20, 30)]; - [_textField setPlaceholder:@"https://www.google.com"]; - [_textField setDelegate:self]; - [_textField setKeyboardType:UIKeyboardTypeURL]; - [_textField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; - [_textField setAutocorrectionType:UITextAutocorrectionTypeNo]; - [_textField setReturnKeyType:UIReturnKeyDone]; - [_textField setClearButtonMode:UITextFieldViewModeWhileEditing]; - [_textField setBorderStyle:UITextBorderStyleRoundedRect]; - [_textField addTarget:self - action:@selector(_textChanged:) - forControlEvents:UIControlEventEditingChanged]; - [[self view] addSubview:_textField]; - - _shortenerLabel = [[UILabel alloc] - initWithFrame:CGRectMake(10, CGRectGetMaxY([_textField frame]) + 10, - bounds.size.width - 20, 40)]; - [_shortenerLabel setFont:[UIFont systemFontOfSize:12]]; - [_shortenerLabel setNumberOfLines:0]; - [[self view] addSubview:_shortenerLabel]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - PWConfigureViewController *__weak weakSelf = self; - [_textField setEnabled:NO]; - - [[[self view] window] setUserInteractionEnabled:NO]; - _hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - [_hud setMode:MBProgressHUDModeIndeterminate]; - [_hud setLabelText:@"Reading"]; - - _delayStartTime = [NSDate timeIntervalSinceReferenceDate]; - _resultURL = nil; - - // Read beacon content. - [[self beacon] connect:^(NSError *error) { - PWConfigureViewController *strongSelf = weakSelf; - [[strongSelf beacon] - readBeaconWithCompletionBlock:^(NSError *error, UBUriBeacon *beacon) { - [strongSelf _connectedWithBeacon:beacon]; - }]; - }]; -} - -- (void)_connectedWithBeacon:(UBUriBeacon *)beacon { - PWConfigureViewController *__weak weakSelf = self; - [PWURLShortener - expandURL:[beacon URI] - completion:^(NSError *error, NSURL *resultURL) { - PWConfigureViewController *strongSelf = weakSelf; - - _resultURL = resultURL; - [strongSelf - _performSelectorAfterWaitDelay:@selector(_readDoneWithError:) - object:error]; - }]; -} - -- (void)_readDoneWithError:(NSError *)error { - [[[self view] window] setUserInteractionEnabled:YES]; - [_hud hide:YES]; - _hud = nil; - - [_textField setText:[_resultURL absoluteString]]; - _connected = YES; - [_textField setEnabled:YES]; - [_textField becomeFirstResponder]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [_textField resignFirstResponder]; - [[self beacon] disconnect:^(NSError *error) { _connected = NO; }]; - [super viewWillDisappear:animated]; -} - -- (void)_textChanged:(id)sender { - NSURL *url = nil; - if ([[_textField text] length] > 0) { - url = [NSURL URLWithString:[_textField text]]; - } - if (url != nil) { - UBUriBeacon *beacon = - [[UBUriBeacon alloc] initWithURI:url txPowerLevel:DEFAULT_TX_POWER_LEVEL]; - if ([beacon isValid]) { - [_shortenerLabel - setTextColor:[UIColor colorWithRed:0.3 green:0.6 blue:0.3 alpha:1.0]]; - [_shortenerLabel setText:@"The URL is valid."]; - } else { - [_shortenerLabel - setTextColor:[UIColor colorWithRed:0.3 green:0.3 blue:1.0 alpha:1.0]]; - [_shortenerLabel - setText: - @"The URL is valid and will be shortened using goo.gl service."]; - } - } else { - [_shortenerLabel setText:nil]; - } -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField { - NSURL *url = [NSURL URLWithString:[_textField text]]; - // Add http:// if there's no scheme. - if ([[url scheme] length] == 0) { - NSString *urlString = - [@"http://" stringByAppendingString:[_textField text]]; - url = [NSURL URLWithString:urlString]; - } - if (url == nil) { - UIAlertView *alert = [[UIAlertView alloc] - initWithTitle:@"The URL is not valid" - message:@"Please check you typed it properly." - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; - return YES; - } - - // Start writing process. - _delayStartTime = [NSDate timeIntervalSinceReferenceDate]; - [_textField resignFirstResponder]; - [self _startProgress]; - - UBUriBeacon *beaconData = - [[UBUriBeacon alloc] initWithURI:url txPowerLevel:DEFAULT_TX_POWER_LEVEL]; - if (![beaconData isValid]) { - [PWURLShortener shortenURL:url - completion:^(NSError *error, NSURL *resultURL) { - [self _validateWithURL:resultURL]; - }]; - } else { - [self _validateWithURL:url]; - } - - return YES; -} - -- (void)_validateWithURL:(NSURL *)url { - UBUriBeacon *beaconData = - [[UBUriBeacon alloc] initWithURI:url txPowerLevel:DEFAULT_TX_POWER_LEVEL]; - if (![beaconData isValid]) { - [self _performSelectorAfterWaitDelay:@selector(_showLongURLError) - object:nil]; - return; - } - - [self _reallyWriteBeaconWithURL:url]; -} - -- (void)_performSelectorAfterWaitDelay:(SEL)selector object:(id)object { - NSTimeInterval delay = - [NSDate timeIntervalSinceReferenceDate] - _delayStartTime; - delay = 1.5 - delay; - if (delay < 0) { - delay = 0; - } - [self performSelector:selector withObject:object afterDelay:delay]; -} - -- (void)_showLongURLError { - [self _stopProgress]; - UIAlertView *alert = [[UIAlertView alloc] - initWithTitle:@"The URL is too long" - message:@"You could use a URL shortener to help." - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; - [_textField becomeFirstResponder]; -} - -- (void)_startProgress { - [[[self view] window] setUserInteractionEnabled:NO]; - - _hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - [_hud setMode:MBProgressHUDModeIndeterminate]; - [_hud setLabelText:@"Writing"]; -} - -- (void)_stopProgress { - [[[self view] window] setUserInteractionEnabled:YES]; - [_hud hide:YES]; - _hud = nil; -} - -- (void)_showWriteDone { - [[[self view] window] setUserInteractionEnabled:NO]; - _hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - _hud.customView = [[UIImageView alloc] - initWithImage:[UIImage imageNamed:@"37x-Checkmark.png"]]; - _hud.mode = MBProgressHUDModeCustomView; - _hud.labelText = @"Completed"; -} - -- (void)_hideWriteDone { - [_hud hide:YES]; - _hud = nil; - - [self _dismissViewController]; - [[[self view] window] setUserInteractionEnabled:YES]; -} - -- (void)_reallyWriteBeaconWithURL:(NSURL *)url { - UBUriBeacon *beaconData = - [[UBUriBeacon alloc] initWithURI:url txPowerLevel:DEFAULT_TX_POWER_LEVEL]; - PWConfigureViewController *__weak weakSelf = self; - [[self beacon] - writeBeacon:beaconData - completionBlock:^(NSError *error) { - PWConfigureViewController *strongSelf = weakSelf; - NSLog(@"write done"); - [strongSelf - _performSelectorAfterWaitDelay:@selector(_writeDoneWithError:) - object:error]; - }]; -} - -- (void)_writeDoneWithError:(NSError *)error { - [self _stopProgress]; - - if (error == nil) { - [self _showWriteDone]; - [self performSelector:@selector(_hideWriteDone) - withObject:nil - afterDelay:1.5]; - return; - } - - [_textField becomeFirstResponder]; - UIAlertView *alert = [[UIAlertView alloc] - initWithTitle:@"An error occurred while writing the beacon" - message:[error localizedDescription] - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; -} - -- (void)_dismissViewController { - [[self navigationController] popViewControllerAnimated:YES]; -} - -@end diff --git a/ios/PhyWeb/UI/PWGradientView.h b/ios/PhyWeb/UI/PWGradientView.h deleted file mode 100644 index 55043203..00000000 --- a/ios/PhyWeb/UI/PWGradientView.h +++ /dev/null @@ -1,28 +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. - */ - -#import - -// This object will show a linear gradient from top to bottom. -@interface PWGradientView : UIView - -// Color to show at the top of the gradient. -@property(nonatomic, retain) UIColor* topColor; - -// Color to show at the bottom of the gradient. -@property(nonatomic, retain) UIColor* bottomColor; - -@end diff --git a/ios/PhyWeb/UI/PWGradientView.m b/ios/PhyWeb/UI/PWGradientView.m deleted file mode 100644 index f6e53ecc..00000000 --- a/ios/PhyWeb/UI/PWGradientView.m +++ /dev/null @@ -1,51 +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. - */ - -#import "PWGradientView.h" - -@implementation PWGradientView { - UIColor* _bottomColor; - UIColor* _topColor; -} - -@synthesize bottomColor = _bottomColor; -@synthesize topColor = _topColor; - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - self.opaque = NO; - return self; -} - -- (void)drawRect:(CGRect)rect { - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGFloat locations[3] = {0., 0.6, 1.}; - CGFloat components[12] = {1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0.}; - CGGradientRef gradient = - CGGradientCreateWithColorComponents(colorSpace, components, locations, 3); - CGRect bounds = [self bounds]; - CGPoint point = bounds.origin; - point.y += bounds.size.height; - CGContextDrawLinearGradient(context, gradient, bounds.origin, point, 0); - CFRelease(gradient); - CFRelease(colorSpace); - - [super drawRect:rect]; -} - -@end diff --git a/ios/PhyWeb/UI/PWPlaceholderView.h b/ios/PhyWeb/UI/PWPlaceholderView.h deleted file mode 100644 index 5eb8a08a..00000000 --- a/ios/PhyWeb/UI/PWPlaceholderView.h +++ /dev/null @@ -1,33 +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. - */ - -#import - -@interface PWPlaceholderView : UIView - -@property(nonatomic, assign) BOOL showLabel; -@property(nonatomic, copy) NSString* label; -@property(nonatomic, assign) BOOL bluetoothEnabled; - -// Start animation. -- (void)start; - -// Stops animation. -- (void)stop; - -- (void)setShowLabel:(BOOL)showLabel animated:(BOOL)animated; - -@end diff --git a/ios/PhyWeb/UI/PWPlaceholderView.m b/ios/PhyWeb/UI/PWPlaceholderView.m deleted file mode 100644 index 1b20fdae..00000000 --- a/ios/PhyWeb/UI/PWPlaceholderView.m +++ /dev/null @@ -1,127 +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. - */ - -#import "PWPlaceholderView.h" - -#import "PWActivityIndicator.h" - -@implementation PWPlaceholderView { - UILabel *_placeholderLabel; - PWActivityIndicator *_placeholderIcon; - UIImageView *_bluetoothDisabledIcon; - BOOL _showLabel; - BOOL _bluetoothEnabled; -} - -@synthesize showLabel = _showLabel; -@synthesize bluetoothEnabled = _bluetoothEnabled; - -- (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - _placeholderIcon = [[PWActivityIndicator alloc] initWithFrame:CGRectZero]; - [self addSubview:_placeholderIcon]; - _bluetoothDisabledIcon = [[UIImageView alloc] initWithFrame:CGRectZero]; - [_bluetoothDisabledIcon setImage:[UIImage imageNamed:@"ScanError.png"]]; - [_bluetoothDisabledIcon setAlpha:0.0]; - [self addSubview:_bluetoothDisabledIcon]; - _placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [_placeholderLabel setTextAlignment:NSTextAlignmentCenter]; - [_placeholderLabel setFont:[UIFont boldSystemFontOfSize:20]]; - [_placeholderLabel setTextColor:[UIColor colorWithWhite:0.8 alpha:1.0]]; - [_placeholderLabel setText:@"No beacons detected"]; - [_placeholderLabel setNumberOfLines:0]; - [_placeholderLabel setAlpha:0.0]; - [self addSubview:_placeholderLabel]; - [_placeholderIcon start]; - return self; -} - -- (void)layoutSubviews { - CGRect bounds = [self bounds]; - if (bounds.size.width > bounds.size.height) { - CGRect frame; - frame.size = - [_placeholderLabel sizeThatFits:CGSizeMake(bounds.size.width - 40, 0)]; - frame.size.width = bounds.size.width - 40; - frame.origin = CGPointMake(20, 10); - [_placeholderLabel setFrame:frame]; - frame.origin.x = (int)((bounds.size.width - 100) / 2); - frame.origin.y = CGRectGetMaxY([_placeholderLabel frame]) + 10; - frame.size.width = 100; - frame.size.height = 100; - frame = CGRectIntegral(frame); - [_placeholderIcon setFrame:frame]; - [_bluetoothDisabledIcon setFrame:frame]; - } else { - CGRect frame; - frame.origin.x = (int)((bounds.size.width - 100) / 2); - frame.origin.y = (bounds.size.height - 20) * 140 / 480; - frame.size.width = 100; - frame.size.height = 100; - frame = CGRectIntegral(frame); - [_placeholderIcon setFrame:frame]; - [_bluetoothDisabledIcon setFrame:frame]; - frame.size = - [_placeholderLabel sizeThatFits:CGSizeMake(bounds.size.width - 40, 0)]; - frame.size.width = bounds.size.width - 40; - frame.origin = CGPointMake( - 20, CGRectGetMinY([_placeholderIcon frame]) - frame.size.height - 10); - [_placeholderLabel setFrame:frame]; - } -} - -- (void)setShowLabel:(BOOL)showLabel { - [self setShowLabel:showLabel animated:YES]; -} - -- (void)setShowLabel:(BOOL)showLabel animated:(BOOL)animated { - if (!animated) { - [_placeholderLabel setAlpha:showLabel ? 1.0 : 0.0]; - } else { - [UIView animateWithDuration:0.25 - animations:^{ - [_placeholderLabel setAlpha:showLabel ? 1.0 : 0.0]; - }]; - } -} - -- (void)setBluetoothEnabled:(BOOL)enabled { - _bluetoothEnabled = enabled; - [UIView animateWithDuration:0.25 - animations:^{ - [_placeholderIcon setAlpha:enabled ? 1.0 : 0.0]; - [_bluetoothDisabledIcon setAlpha:enabled ? 0.0 : 1.0]; - }]; -} - -- (void)setLabel:(NSString *)label { - [_placeholderLabel setText:label]; - [self setNeedsLayout]; -} - -- (NSString *)label { - return [_placeholderLabel text]; -} - -- (void)start { - [_placeholderIcon start]; -} - -- (void)stop { - [_placeholderIcon stop]; -} - -@end diff --git a/ios/PhyWeb/UI/PWSettingsViewController.h b/ios/PhyWeb/UI/PWSettingsViewController.h deleted file mode 100644 index 01f5497c..00000000 --- a/ios/PhyWeb/UI/PWSettingsViewController.h +++ /dev/null @@ -1,22 +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. - */ - -#import - -// This part of the UI will show instructions about how to . -@interface PWSettingsViewController : UIViewController - -@end diff --git a/ios/PhyWeb/UI/PWSettingsViewController.m b/ios/PhyWeb/UI/PWSettingsViewController.m deleted file mode 100644 index f49717ff..00000000 --- a/ios/PhyWeb/UI/PWSettingsViewController.m +++ /dev/null @@ -1,258 +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. - */ - -#import "PWSettingsViewController.h" - -#import -#import -#import - -#import "PWBeaconManager.h" -#import "PWConfigureViewController.h" -#import "PWPlaceholderView.h" -#import "PWSimpleWebViewController.h" - -@interface PWSettingsViewController () < - CBCentralManagerDelegate, UITableViewDataSource, UITableViewDelegate> - -@end - -@implementation PWSettingsViewController { - id _registeredBlock; - PWPlaceholderView *_placeholderView; - CBCentralManager *_centralManager; - UILabel *_versionLabel; -} - -- (id)initWithNibName:(NSString *)nibNameOrNil - bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - - UIBarButtonItem *cancelButton = - [[UIBarButtonItem alloc] initWithTitle:@"Cancel" - style:UIBarButtonItemStylePlain - target:self - action:@selector(_cancel:)]; - [[self navigationItem] setLeftBarButtonItem:cancelButton]; - - _centralManager = - [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; - - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGRect bounds = [[self view] bounds]; - CGRect frame = bounds; - frame.origin.y = 64; - frame.size.height -= 64; - _placeholderView = [[PWPlaceholderView alloc] initWithFrame:frame]; - [_placeholderView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth]; - [_placeholderView setShowLabel:YES]; - UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] - initWithTarget:self - action:@selector(_enableDebugMode)]; - [doubleTapRecognizer setNumberOfTapsRequired:2]; - [_placeholderView addGestureRecognizer:doubleTapRecognizer]; - [self _updateLayoutForSize:bounds.size]; - - [[self view] addSubview:_placeholderView]; - [self _updatedPlaceholderViewState]; - - frame = CGRectMake(0, bounds.size.height - 120, bounds.size.width, 90); - UITableView *tableView = - [[UITableView alloc] initWithFrame:frame style:UITableViewStylePlain]; - [tableView setBounces:NO]; - [tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; - [tableView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | - UIViewAutoresizingFlexibleTopMargin]; - [tableView setDataSource:self]; - [tableView setDelegate:self]; - [[self view] addSubview:tableView]; - - frame = bounds; - frame.origin.x = 20; - frame.origin.y = bounds.size.height - 30; - frame.size.height = 20; - frame.size.width = bounds.size.width - 30; - _versionLabel = [[UILabel alloc] initWithFrame:frame]; - [_versionLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth | - UIViewAutoresizingFlexibleTopMargin]; - [_versionLabel setFont:[UIFont boldSystemFontOfSize:14]]; - [_versionLabel setTextAlignment:NSTextAlignmentCenter]; - [_versionLabel setTextColor:[UIColor colorWithWhite:0.8 alpha:1.0]]; - NSString *version = [[[NSBundle mainBundle] infoDictionary] - objectForKey:@"CFBundleShortVersionString"]; - NSString *buildNumber = [[[NSBundle mainBundle] infoDictionary] - objectForKey:(id)kCFBundleVersionKey]; - - [_versionLabel setText:[NSString stringWithFormat:@"version %@ build %@", - version, buildNumber]]; - [[self view] addSubview:_versionLabel]; - - [[self view] setBackgroundColor:[UIColor whiteColor]]; -} - -- (void)viewWillTransitionToSize:(CGSize)size - withTransitionCoordinator: - (id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [self _updateLayoutForSize:size]; -} - -- (void)_updateLayoutForSize:(CGSize)size { - CGRect frame = [_placeholderView frame]; - if (size.width > size.height) { - frame.origin.y = 33; - frame.size.height -= 33; - } else { - frame.origin.y = 64; - frame.size.height -= 64; - } - [_placeholderView setFrame:frame]; -} - -- (void)viewDidDisappear:(BOOL)animated { - [_placeholderView stop]; -} - -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - [self _updatedPlaceholderViewState]; -} - -- (void)_enableDebugMode { - BOOL debugMode = - ![[NSUserDefaults standardUserDefaults] boolForKey:@"DebugMode"]; - [[NSUserDefaults standardUserDefaults] setBool:debugMode forKey:@"DebugMode"]; - - MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - [hud setMode:MBProgressHUDModeText]; - [hud setLabelText:debugMode ? @"Debug Mode Enabled" : @"Debug Mode Disabled"]; - [hud hide:YES afterDelay:1.5]; -} - -- (void)_updatedPlaceholderViewState { - BOOL enabled = ([_centralManager state] == CBCentralManagerStatePoweredOn); - [_placeholderView setBluetoothEnabled:enabled]; - if (enabled) { - [_placeholderView - setLabel: - @"Put the beacon in configuration mode in order to set it up."]; - } else { - [_placeholderView - setLabel:@"Please turn on bluetooth in order to configure beacons."]; - } -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - PWSettingsViewController *__weak weakSelf = self; - _registeredBlock = - [[PWBeaconManager sharedManager] registerConfigurationChangeBlock:^{ - PWSettingsViewController *strongSelf = weakSelf; - [strongSelf _openConfigurationView]; - }]; -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [[PWBeaconManager sharedManager] - unregisterConfigurationChangeBlock:_registeredBlock]; - _registeredBlock = nil; -} - -- (void)_openConfigurationView { - // Trigger configuration panel if there's a one configurable beacon. - if ([[[PWBeaconManager sharedManager] configurableBeacons] count] > 0) { - [[PWBeaconManager sharedManager] - unregisterConfigurationChangeBlock:_registeredBlock]; - _registeredBlock = nil; - - UBConfigurableUriBeacon *beacon = - [[[PWBeaconManager sharedManager] configurableBeacons] objectAtIndex:0]; - PWConfigureViewController *configureViewController = - [[PWConfigureViewController alloc] initWithNibName:nil bundle:nil]; - [configureViewController setBeacon:beacon]; - [[self navigationController] pushViewController:configureViewController - animated:YES]; - } -} - -- (void)_cancel:(id)sender { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -#pragma mark - Table view data source - -- (NSInteger)tableView:(UITableView *)tableView - numberOfRowsInSection:(NSInteger)section { - return 2; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView - cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault - reuseIdentifier:@"Cell"]; - [[cell textLabel] setTextColor:[UIColor colorWithWhite:0.4 alpha:1.0]]; - [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; - } - switch ([indexPath row]) { - case 0: - [[cell textLabel] setText:@"Getting Started"]; - break; - case 1: - [[cell textLabel] setText:@"Open Source Licenses"]; - break; - } - - return cell; -} - -- (void)tableView:(UITableView *)tableView - didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - switch ([indexPath row]) { - case 0: { - PWSimpleWebViewController *controller = [[PWSimpleWebViewController alloc] - initWithURL:[NSURL URLWithString:@"http://google.github.io/" - @"physical-web/mobile/ios/" - @"getting-started.html"]]; - [controller setTitle:@"Getting Started"]; - [[self navigationController] pushViewController:controller animated:YES]; - break; - } - case 1: { - NSString *path = - [[NSBundle mainBundle] pathForResource:@"licenses" ofType:@"html"]; - NSData *data = [NSData dataWithContentsOfFile:path]; - NSString *htmlString = - [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - PWSimpleWebViewController *controller = - [[PWSimpleWebViewController alloc] initWithHTMLString:htmlString]; - [controller setTitle:@"Licenses"]; - [[self navigationController] pushViewController:controller animated:YES]; - break; - } - } -} - -@end diff --git a/ios/PhyWeb/UI/PWSignalStrengthView.h b/ios/PhyWeb/UI/PWSignalStrengthView.h deleted file mode 100644 index b9a72ede..00000000 --- a/ios/PhyWeb/UI/PWSignalStrengthView.h +++ /dev/null @@ -1,27 +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. - */ - -#import - -// This object will show a signal strength icon that will change -// depending on the quality. -@interface PWSignalStrengthView : UIView - -// This is a linear value between 0 and 100 -// 0: bad quality, 100: excellent quality. -@property(nonatomic, assign) NSInteger quality; - -@end diff --git a/ios/PhyWeb/UI/PWSignalStrengthView.m b/ios/PhyWeb/UI/PWSignalStrengthView.m deleted file mode 100644 index dfca68e0..00000000 --- a/ios/PhyWeb/UI/PWSignalStrengthView.m +++ /dev/null @@ -1,56 +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. - */ - -#import "PWSignalStrengthView.h" - -@implementation PWSignalStrengthView { - NSInteger _quality; -} - -@synthesize quality = _quality; - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - self.opaque = NO; - return self; -} - -- (void)setQuality:(NSInteger)quality { - _quality = quality; - [self setNeedsDisplay]; -} - -- (void)drawRect:(CGRect)rect { - [[UIColor blackColor] setFill]; - if (_quality >= 5) { - CGRect rect = CGRectMake(0, 9, 3, 6); - UIRectFill(rect); - } - if (_quality >= 25) { - CGRect rect = CGRectMake(4, 6, 3, 9); - UIRectFill(rect); - } - if (_quality >= 50) { - CGRect rect = CGRectMake(8, 3, 3, 12); - UIRectFill(rect); - } - if (_quality >= 75) { - CGRect rect = CGRectMake(12, 0, 3, 15); - UIRectFill(rect); - } -} - -@end diff --git a/ios/PhyWeb/UI/PWSimpleWebViewController.h b/ios/PhyWeb/UI/PWSimpleWebViewController.h deleted file mode 100644 index 8483a76a..00000000 --- a/ios/PhyWeb/UI/PWSimpleWebViewController.h +++ /dev/null @@ -1,38 +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. - */ - -#import - -@protocol PWSimpleWebViewControllerDelegate; - -@interface PWSimpleWebViewController : UIViewController - -@property(nonatomic, copy) NSString *title; -@property(nonatomic, assign) id delegate; -@property(nonatomic, assign) BOOL proceedButtonVisible; - -- (instancetype)initWithURL:(NSURL *)url; -- (instancetype)initWithHTMLString:(NSString *)htmlString; - -@end - -@protocol PWSimpleWebViewControllerDelegate - -@optional -- (void)simpleWebViewControllerProceedPressed: - (PWSimpleWebViewController *)controller; - -@end diff --git a/ios/PhyWeb/UI/PWSimpleWebViewController.m b/ios/PhyWeb/UI/PWSimpleWebViewController.m deleted file mode 100644 index ea760575..00000000 --- a/ios/PhyWeb/UI/PWSimpleWebViewController.m +++ /dev/null @@ -1,93 +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. - */ - -#import "PWSimpleWebViewController.h" - -#import - -@interface PWSimpleWebViewController () - -@end - -@implementation PWSimpleWebViewController { - WKWebView *_webView; - NSURL *_url; - NSString *_title; - NSString *_htmlString; -} - -@synthesize title = _title; - -- (instancetype)initWithURL:(NSURL *)url { - self = [super initWithNibName:nil bundle:nil]; - _url = url; - return self; -} - -- (instancetype)initWithHTMLString:(NSString *)htmlString { - self = [super initWithNibName:nil bundle:nil]; - _htmlString = htmlString; - return self; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGRect frame = [[self view] bounds]; - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; - _webView = [[WKWebView alloc] initWithFrame:frame]; - [_webView setNavigationDelegate:self]; - if (_url != nil) { - [_webView loadRequest:[NSURLRequest requestWithURL:_url]]; - } else { - [_webView loadHTMLString:_htmlString baseURL:nil]; - } - [[self view] addSubview:_webView]; - - if ([self proceedButtonVisible]) { - UIBarButtonItem *button = - [[UIBarButtonItem alloc] initWithTitle:@"Proceed" - style:UIBarButtonItemStyleDone - target:self - action:@selector(_proceed:)]; - [[self navigationItem] setRightBarButtonItem:button]; - } -} - -- (void)viewDidDisappear:(BOOL)animated { - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; -} - -- (void)webView:(WKWebView *)webView - didFailNavigation:(WKNavigation *)navigation - withError:(NSError *)error { - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; -} - -- (void)webView:(WKWebView *)webView - didFinishNavigation:(WKNavigation *)navigation { - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; -} - -- (void)_proceed:(id)sender { - if ([[self delegate] - respondsToSelector:@selector( - simpleWebViewControllerProceedPressed:)]) { - [[self delegate] simpleWebViewControllerProceedPressed:self]; - } -} - -@end diff --git a/ios/PhyWeb/main.m b/ios/PhyWeb/main.m deleted file mode 100644 index 397f341b..00000000 --- a/ios/PhyWeb/main.m +++ /dev/null @@ -1,25 +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. - */ - -#import -#import "AppDelegate.h" - -int main(int argc, char* argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, - NSStringFromClass([AppDelegate class])); - } -} diff --git a/ios/PhyWeb/third-party/JBChartView/JBBarChartView.h b/ios/PhyWeb/third-party/JBChartView/JBBarChartView.h deleted file mode 100644 index 72bba2b1..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBBarChartView.h +++ /dev/null @@ -1,150 +0,0 @@ -// -// JBBarChartView.h -// JBChartView -// -// Created by Terry Worona on 9/3/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -// Views -#import "JBChartView.h" - -@class JBBarChartView; - -@protocol JBBarChartViewDataSource - -@required - -/** - * The number of bars in a given bar chart is the number of vertical views shown along the x-axis. - * - * @param barChartView The bar chart object requesting this information. - * - * @return Number of bars in the given chart, displayed horizontally along the chart's x-axis. - */ -- (NSUInteger)numberOfBarsInBarChartView:(JBBarChartView *)barChartView; - -@optional - -/** - * A UIView subclass representing the bar at a particular index. - * - * Default: solid black UIView. - * - * @param barChartView The bar chart object requesting this information. - * @param index The 0-based index of a given bar (left to right, x-axis). - * - * @return A UIView subclass. The view will automatically be resized by the chart during creation (ie. no need to set the frame). - */ -- (UIView *)barChartView:(JBBarChartView *)barChartView barViewAtIndex:(NSUInteger)index; - -@end - -@protocol JBBarChartViewDelegate - -@required - -/** - * Height for a bar at a given index (left to right). There is no ceiling on the the height; - * the chart will automatically normalize all values between the overal min and max heights. - * - * @param barChartView The bar chart object requesting this information. - * @param index The 0-based index of a given bar (left to right, x-axis). - * - * @return The y-axis height of the supplied bar index (x-axis) - */ -- (CGFloat)barChartView:(JBBarChartView *)barChartView heightForBarViewAtIndex:(NSUInteger)index; - -@optional - -/** - * Occurs when a touch gesture event occurs on a given bar (chart must be expanded). - * and the selection must occur within the bounds of the chart. - * - * @param barChartView A bar chart object informing the delegate about the new selection. - * @param index The 0-based index of a given bar (left to right, x-axis). - * @param touchPoint The touch point in relation to the chart's bounds (excludes footer and header). - */ -- (void)barChartView:(JBBarChartView *)barChartView didSelectBarAtIndex:(NSUInteger)index touchPoint:(CGPoint)touchPoint; -- (void)barChartView:(JBBarChartView *)barChartView didSelectBarAtIndex:(NSUInteger)index; - -/** - * Occurs when selection ends by either ending a touch event or selecting an area that is outside the view's bounds. - * For selection start events, see: didSelectBarAtIndex... - * - * @param barChartView A bar chart object informing the delegate about the deselection. - */ -- (void)didDeselectBarChartView:(JBBarChartView *)barChartView; - -/** - * If you already implement barChartView:barViewAtIndex: delegate - this method has no effect. - * If a custom UIView isn't supplied, a flat bar will be made automatically (default color black). - * - * Default: if none specified - calls barChartView:barViewAtIndex:. - * - * @param barChartView The bar chart object requesting this information. - * @param index The 0-based index of a given bar (left to right, x-axis). - * - * @return The color to be used to color a bar in the chart. - */ -- (UIColor *)barChartView:(JBBarChartView *)barChartView colorForBarViewAtIndex:(NSUInteger)index; - -/** - * The selection color to be overlayed on a bar during touch events. - * The color is automatically faded to transparent (vertically). The property showsVerticalSelection - * must be YES for the color to apply. - * - * Default: white color (faded to transparent). - * - * @param barChartView The bar chart object requesting this information. - * - * @return The color to be used on each bar selection. - */ -- (UIColor *)barSelectionColorForBarChartView:(JBBarChartView *)barChartView; - -/** - * Horizontal padding between bars. - * - * Default: 'best-guess' algorithm based on the the total number of bars and width of the chart. - * - * @param barChartView The bar chart object requesting this information. - * - * @return Horizontal width (in pixels) between each bar. - */ -- (CGFloat)barPaddingForBarChartView:(JBBarChartView *)barChartView; - -@end - -@interface JBBarChartView : JBChartView - -@property (nonatomic, weak) id dataSource; -@property (nonatomic, weak) id delegate; - -/** - * Vertical highlight overlayed on bar during touch events. - * - * Default: YES. - */ -@property (nonatomic, assign) BOOL showsVerticalSelection; - -/* - * Bars can be (vertically) positoned top to bottom instead of bottom up. - * If this property is set to YES, both the bar and the selection view will be inverted. - * For the inverted orientation to take effect, reloadData must be called. - * - * Default: NO. - */ -@property (nonatomic, assign, getter=isInverted) BOOL inverted; - -/** - * The bar view at a particular index. - * - * Default: nil. - * - * @param index The 0-based index of a given bar (left to right, x-axis). - * - * @return The UIView representing the bar view at a given index or nil if the index is out of range. - */ -- (UIView *)barViewAtIndex:(NSUInteger)index; - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/JBBarChartView.m b/ios/PhyWeb/third-party/JBChartView/JBBarChartView.m deleted file mode 100644 index 665f140b..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBBarChartView.m +++ /dev/null @@ -1,645 +0,0 @@ -// -// JBBarChartView.m -// Nudge -// -// Created by Terry Worona on 9/3/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -#import "JBBarChartView.h" - -// Numerics -CGFloat static const kJBBarChartViewBarBasePaddingMutliplier = 50.0f; -CGFloat static const kJBBarChartViewUndefinedCachedHeight = -1.0f; -CGFloat static const kJBBarChartViewStateAnimationDuration = 0.05f; -CGFloat static const kJBBarChartViewStatePopOffset = 10.0f; -NSInteger static const kJBBarChartViewUndefinedBarIndex = -1; - -// Colors (JBChartView) -static UIColor *kJBBarChartViewDefaultBarColor = nil; - -@interface JBChartView (Private) - -- (BOOL)hasMaximumValue; -- (BOOL)hasMinimumValue; - -@end - -@interface JBBarChartView () - -@property (nonatomic, strong) NSDictionary *chartDataDictionary; // key = column, value = height -@property (nonatomic, strong) NSArray *barViews; -@property (nonatomic, strong) NSArray *cachedBarViewHeights; -@property (nonatomic, assign) CGFloat barPadding; -@property (nonatomic, assign) CGFloat cachedMaxHeight; -@property (nonatomic, assign) CGFloat cachedMinHeight; -@property (nonatomic, strong) JBChartVerticalSelectionView *verticalSelectionView; -@property (nonatomic, assign) BOOL verticalSelectionViewVisible; - -// Initialization -- (void)construct; - -// View quick accessors -- (CGFloat)availableHeight; -- (CGFloat)normalizedHeightForRawHeight:(NSNumber*)rawHeight; -- (CGFloat)barWidth; - -// Touch helpers -- (NSInteger)barViewIndexForPoint:(CGPoint)point; -- (UIView *)barViewForForPoint:(CGPoint)point; -- (void)touchesBeganOrMovedWithTouches:(NSSet *)touches; -- (void)touchesEndedOrCancelledWithTouches:(NSSet *)touches; - -// Setters -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible animated:(BOOL)animated; - -@end - -@implementation JBBarChartView - -@dynamic dataSource; -@dynamic delegate; - -#pragma mark - Alloc/Init - -+ (void)initialize -{ - if (self == [JBBarChartView class]) - { - kJBBarChartViewDefaultBarColor = [UIColor blackColor]; - } -} - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (self) - { - [self construct]; - } - return self; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - [self construct]; - } - return self; -} - -- (id)init -{ - self = [super init]; - if (self) - { - [self construct]; - } - return self; -} - -- (void)construct -{ - _showsVerticalSelection = YES; - _cachedMinHeight = kJBBarChartViewUndefinedCachedHeight; - _cachedMaxHeight = kJBBarChartViewUndefinedCachedHeight; -} - -#pragma mark - Memory Management - -- (void)dealloc -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self]; -} - -#pragma mark - Data - -- (void)reloadData -{ - // reset cached max height - self.cachedMinHeight = kJBBarChartViewUndefinedCachedHeight; - self.cachedMaxHeight = kJBBarChartViewUndefinedCachedHeight; - - /* - * The data collection holds all position information: - * constructed via datasource and delegate functions - */ - dispatch_block_t createDataDictionaries = ^{ - - // Grab the count - NSAssert([self.dataSource respondsToSelector:@selector(numberOfBarsInBarChartView:)], @"JBBarChartView // datasource must implement - (NSUInteger)numberOfBarsInBarChartView:(JBBarChartView *)barChartView"); - NSUInteger dataCount = [self.dataSource numberOfBarsInBarChartView:self]; - - // Build up the data collection - NSAssert([self.delegate respondsToSelector:@selector(barChartView:heightForBarViewAtIndex:)], @"JBBarChartView // delegate must implement - (CGFloat)barChartView:(JBBarChartView *)barChartView heightForBarViewAtIndex:(NSUInteger)index"); - NSMutableDictionary *dataDictionary = [NSMutableDictionary dictionary]; - for (NSUInteger index=0; index= 0, @"JBBarChartView // datasource function - (CGFloat)barChartView:(JBBarChartView *)barChartView heightForBarViewAtIndex:(NSUInteger)index must return a CGFloat >= 0"); - [dataDictionary setObject:[NSNumber numberWithFloat:height] forKey:[NSNumber numberWithInt:(int)index]]; - } - self.chartDataDictionary = [NSDictionary dictionaryWithDictionary:dataDictionary]; - }; - - /* - * Determines the padding between bars as a function of # of bars - */ - dispatch_block_t createBarPadding = ^{ - if ([self.delegate respondsToSelector:@selector(barPaddingForBarChartView:)]) - { - self.barPadding = [self.delegate barPaddingForBarChartView:self]; - } - else - { - NSUInteger totalBars = [[self.chartDataDictionary allKeys] count]; - self.barPadding = (1/(float)totalBars) * kJBBarChartViewBarBasePaddingMutliplier; - } - }; - - /* - * Creates a new bar graph view using the previously calculated data model - */ - dispatch_block_t createBars = ^{ - - // Remove old bars - for (UIView *barView in self.barViews) - { - [barView removeFromSuperview]; - } - - self.cachedBarViewHeights = nil; - - CGFloat xOffset = 0; - NSUInteger index = 0; - NSMutableArray *mutableBarViews = [NSMutableArray array]; - NSMutableArray *mutableCachedBarViewHeights = [NSMutableArray array]; - for (NSNumber *key in [[self.chartDataDictionary allKeys] sortedArrayUsingSelector:@selector(compare:)]) - { - UIView *barView = nil; // since all bars are visible at once, no need to cache this view - if ([self.dataSource respondsToSelector:@selector(barChartView:barViewAtIndex:)]) - { - barView = [self.dataSource barChartView:self barViewAtIndex:index]; - NSAssert(barView != nil, @"JBBarChartView // datasource function - (UIView *)barChartView:(JBBarChartView *)barChartView barViewAtIndex:(NSUInteger)index must return a non-nil UIView subclass"); - } - else - { - barView = [[UIView alloc] init]; - UIColor *backgroundColor = nil; - - if ([self.delegate respondsToSelector:@selector(barChartView:colorForBarViewAtIndex:)]) - { - backgroundColor = [self.delegate barChartView:self colorForBarViewAtIndex:index]; - NSAssert(backgroundColor != nil, @"JBBarChartView // delegate function - (UIColor *)barChartView:(JBBarChartView *)barChartView colorForBarViewAtIndex:(NSUInteger)index must return a non-nil UIColor"); - } - else - { - backgroundColor = kJBBarChartViewDefaultBarColor; - } - - barView.backgroundColor = backgroundColor; - } - - barView.tag = index; - - CGFloat height = [self normalizedHeightForRawHeight:[self.chartDataDictionary objectForKey:key]]; - barView.frame = CGRectMake(xOffset, self.bounds.size.height - height - self.footerView.frame.size.height, [self barWidth], height); - [mutableBarViews addObject:barView]; - [mutableCachedBarViewHeights addObject:[NSNumber numberWithFloat:height]]; - - // Add new bar - if (self.footerView) - { - [self insertSubview:barView belowSubview:self.footerView]; - } - else - { - [self addSubview:barView]; - } - - xOffset += ([self barWidth] + self.barPadding); - index++; - } - self.barViews = [NSArray arrayWithArray:mutableBarViews]; - self.cachedBarViewHeights = [NSArray arrayWithArray:mutableCachedBarViewHeights]; - }; - - /* - * Creates a vertical selection view for touch events - */ - dispatch_block_t createSelectionView = ^{ - - // Remove old selection bar - if (self.verticalSelectionView) - { - [self.verticalSelectionView removeFromSuperview]; - self.verticalSelectionView = nil; - } - - CGFloat verticalSelectionViewHeight = self.bounds.size.height - self.headerView.frame.size.height - self.footerView.frame.size.height - self.headerPadding - self.footerPadding; - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoHeaderPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoHeaderPaddingForChartView:self]) - { - verticalSelectionViewHeight += self.headerPadding; - } - } - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoFooterPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoFooterPaddingForChartView:self]) - { - verticalSelectionViewHeight += self.footerPadding; - } - } - - self.verticalSelectionView = [[JBChartVerticalSelectionView alloc] initWithFrame:CGRectMake(0, 0, [self barWidth], verticalSelectionViewHeight)]; - self.verticalSelectionView.alpha = 0.0; - self.verticalSelectionView.hidden = !self.showsVerticalSelection; - if ([self.delegate respondsToSelector:@selector(barSelectionColorForBarChartView:)]) - { - UIColor *selectionViewBackgroundColor = [self.delegate barSelectionColorForBarChartView:self]; - NSAssert(selectionViewBackgroundColor != nil, @"JBBarChartView // delegate function - (UIColor *)barSelectionColorForBarChartView:(JBBarChartView *)barChartView must return a non-nil UIColor"); - self.verticalSelectionView.bgColor = selectionViewBackgroundColor; - } - - // Add new selection bar - if (self.footerView) - { - [self insertSubview:self.verticalSelectionView belowSubview:self.footerView]; - } - else - { - [self addSubview:self.verticalSelectionView]; - } - - self.verticalSelectionView.transform = self.inverted ? CGAffineTransformMakeScale(1.0, -1.0) : CGAffineTransformIdentity; - }; - - createDataDictionaries(); - createBarPadding(); - createBars(); - createSelectionView(); - - // Position header and footer - self.headerView.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.headerView.frame.size.height); - self.footerView.frame = CGRectMake(self.bounds.origin.x, self.bounds.size.height - self.footerView.frame.size.height, self.bounds.size.width, self.footerView.frame.size.height); - - // Refresh state - [self setState:self.state animated:NO force:YES callback:nil]; -} - -#pragma mark - View Quick Accessors - -- (CGFloat)availableHeight -{ - return self.bounds.size.height - self.headerView.frame.size.height - self.footerView.frame.size.height - self.headerPadding - self.footerPadding; -} - -- (CGFloat)normalizedHeightForRawHeight:(NSNumber*)rawHeight -{ - CGFloat minHeight = [self minimumValue]; - CGFloat maxHeight = [self maximumValue]; - CGFloat value = [rawHeight floatValue]; - - if ((maxHeight - minHeight) <= 0) - { - return 0; - } - - return ((value - minHeight) / (maxHeight - minHeight)) * [self availableHeight]; -} - -- (CGFloat)barWidth -{ - NSUInteger barCount = [[self.chartDataDictionary allKeys] count]; - if (barCount > 0) - { - CGFloat totalPadding = (barCount - 1) * self.barPadding; - CGFloat availableWidth = self.bounds.size.width - totalPadding; - return availableWidth / barCount; - } - return 0; -} - -#pragma mark - Setters - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated force:(BOOL)force callback:(void (^)())callback -{ - [super setState:state animated:animated force:force callback:callback]; - - __weak JBBarChartView* weakSelf = self; - - void (^updateBarView)(UIView *barView, BOOL popBar); - - updateBarView = ^(UIView *barView, BOOL popBar) { - if (weakSelf.inverted) - { - if (weakSelf.state == JBChartViewStateExpanded) - { - if (popBar) - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.headerView.frame.size.height + weakSelf.headerPadding, barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] + kJBBarChartViewStatePopOffset); - } - else - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.headerView.frame.size.height + weakSelf.headerPadding, barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue]); - } - } - else if (weakSelf.state == JBChartViewStateCollapsed) - { - if (popBar) - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.headerView.frame.size.height + weakSelf.headerPadding, barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] + kJBBarChartViewStatePopOffset); - } - else - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.headerView.frame.size.height + weakSelf.headerPadding, barView.frame.size.width, 0.0f); - } - } - } - else - { - if (weakSelf.state == JBChartViewStateExpanded) - { - if (popBar) - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.bounds.size.height - weakSelf.footerView.frame.size.height - weakSelf.footerPadding - [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] - kJBBarChartViewStatePopOffset, barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] + kJBBarChartViewStatePopOffset); - } - else - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.bounds.size.height - weakSelf.footerView.frame.size.height - weakSelf.footerPadding - [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue], barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue]); - } - } - else if (weakSelf.state == JBChartViewStateCollapsed) - { - if (popBar) - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.bounds.size.height - weakSelf.footerView.frame.size.height - weakSelf.footerPadding - [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] - kJBBarChartViewStatePopOffset, barView.frame.size.width, [[weakSelf.cachedBarViewHeights objectAtIndex:barView.tag] floatValue] + kJBBarChartViewStatePopOffset); - } - else - { - barView.frame = CGRectMake(barView.frame.origin.x, weakSelf.bounds.size.height, barView.frame.size.width, 0.0f); - } - } - } - }; - - dispatch_block_t callbackCopy = [callback copy]; - - if ([self.barViews count] > 0) - { - if (animated) - { - NSUInteger index = 0; - for (UIView *barView in self.barViews) - { - [UIView animateWithDuration:kJBBarChartViewStateAnimationDuration delay:(kJBBarChartViewStateAnimationDuration * 0.5) * index options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - updateBarView(barView, YES); - } completion:^(BOOL finished) { - [UIView animateWithDuration:kJBBarChartViewStateAnimationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - updateBarView(barView, NO); - } completion:^(BOOL lastBarFinished) { - if ((NSUInteger)barView.tag == [self.barViews count] - 1) - { - if (callbackCopy) - { - callbackCopy(); - } - } - }]; - }]; - index++; - } - } - else - { - for (UIView *barView in self.barViews) - { - updateBarView(barView, NO); - } - if (callbackCopy) - { - callbackCopy(); - } - } - } - else - { - if (callbackCopy) - { - callbackCopy(); - } - } -} - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated callback:(void (^)())callback -{ - [self setState:state animated:animated force:NO callback:callback]; -} - -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible animated:(BOOL)animated -{ - _verticalSelectionViewVisible = verticalSelectionViewVisible; - - if (animated) - { - [UIView animateWithDuration:kJBChartViewDefaultAnimationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.verticalSelectionView.alpha = self.verticalSelectionViewVisible ? 1.0 : 0.0; - } completion:nil]; - } - else - { - self.verticalSelectionView.alpha = _verticalSelectionViewVisible ? 1.0 : 0.0; - } -} - -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible -{ - [self setVerticalSelectionViewVisible:verticalSelectionViewVisible animated:NO]; -} - -- (void)setShowsVerticalSelection:(BOOL)showsVerticalSelection -{ - _showsVerticalSelection = showsVerticalSelection; - self.verticalSelectionView.hidden = _showsVerticalSelection ? NO : YES; -} - -#pragma mark - Getters - -- (CGFloat)cachedMinHeight -{ - if(_cachedMinHeight == kJBBarChartViewUndefinedCachedHeight) - { - NSArray *chartValues = [[NSMutableArray arrayWithArray:[self.chartDataDictionary allValues]] sortedArrayUsingSelector:@selector(compare:)]; - _cachedMinHeight = [[chartValues firstObject] floatValue]; - } - return _cachedMinHeight; -} - -- (CGFloat)cachedMaxHeight -{ - if (_cachedMaxHeight == kJBBarChartViewUndefinedCachedHeight) - { - NSArray *chartValues = [[NSMutableArray arrayWithArray:[self.chartDataDictionary allValues]] sortedArrayUsingSelector:@selector(compare:)]; - _cachedMaxHeight = [[chartValues lastObject] floatValue]; - } - return _cachedMaxHeight; -} - -- (CGFloat)minimumValue -{ - if ([self hasMinimumValue]) - { - return fminf(self.cachedMinHeight, [super minimumValue]); - } - return self.cachedMinHeight; -} - -- (CGFloat)maximumValue -{ - if ([self hasMaximumValue]) - { - return fmaxf(self.cachedMaxHeight, [super maximumValue]); - } - return self.cachedMaxHeight; -} - -- (UIView *)barViewAtIndex:(NSUInteger)index -{ - if (index < [self.barViews count]) - { - return [self.barViews objectAtIndex:index]; - } - return nil; -} - -#pragma mark - Touch Helpers - -- (NSInteger)barViewIndexForPoint:(CGPoint)point -{ - NSUInteger index = 0; - NSUInteger selectedIndex = kJBBarChartViewUndefinedBarIndex; - - if (point.x < 0 || point.x > self.bounds.size.width) - { - return selectedIndex; - } - - CGFloat padding = ceil(self.barPadding * 0.5); - for (UIView *barView in self.barViews) - { - CGFloat minX = CGRectGetMinX(barView.frame) - padding; - CGFloat maxX = CGRectGetMaxX(barView.frame) + padding; - if ((point.x >= minX) && (point.x <= maxX)) - { - selectedIndex = index; - break; - } - index++; - } - return selectedIndex; -} - -- (UIView *)barViewForForPoint:(CGPoint)point -{ - UIView *barView = nil; - NSInteger selectedIndex = [self barViewIndexForPoint:point]; - if (selectedIndex >= 0) - { - return [self.barViews objectAtIndex:[self barViewIndexForPoint:point]]; - } - return barView; -} - -- (void)touchesBeganOrMovedWithTouches:(NSSet *)touches -{ - if (self.state == JBChartViewStateCollapsed || [[self.chartDataDictionary allKeys] count] <= 0) - { - return; - } - - UITouch *touch = [touches anyObject]; - CGPoint touchPoint = [touch locationInView:self]; - UIView *barView = [self barViewForForPoint:touchPoint]; - if (barView == nil) - { - [self setVerticalSelectionViewVisible:NO animated:YES]; - return; - } - CGRect barViewFrame = barView.frame; - CGRect selectionViewFrame = self.verticalSelectionView.frame; - selectionViewFrame.origin.x = barViewFrame.origin.x; - selectionViewFrame.size.width = barViewFrame.size.width; - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoHeaderPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoHeaderPaddingForChartView:self]) - { - selectionViewFrame.origin.y = self.headerView.frame.size.height; - } - else - { - selectionViewFrame.origin.y = self.headerView.frame.size.height + self.headerPadding; - } - } - else - { - selectionViewFrame.origin.y = self.headerView.frame.size.height + self.headerPadding; - } - - self.verticalSelectionView.frame = selectionViewFrame; - [self setVerticalSelectionViewVisible:YES animated:YES]; - - if ([self.delegate respondsToSelector:@selector(barChartView:didSelectBarAtIndex:touchPoint:)]) - { - [self.delegate barChartView:self didSelectBarAtIndex:[self barViewIndexForPoint:touchPoint] touchPoint:touchPoint]; - } - - if ([self.delegate respondsToSelector:@selector(barChartView:didSelectBarAtIndex:)]) - { - [self.delegate barChartView:self didSelectBarAtIndex:[self barViewIndexForPoint:touchPoint]]; - } -} - -- (void)touchesEndedOrCancelledWithTouches:(NSSet *)touches -{ - if (self.state == JBChartViewStateCollapsed || [[self.chartDataDictionary allKeys] count] <= 0) - { - return; - } - - [self setVerticalSelectionViewVisible:NO animated:YES]; - - if ([self.delegate respondsToSelector:@selector(didDeselectBarChartView:)]) - { - [self.delegate didDeselectBarChartView:self]; - } -} - -#pragma mark - Touches - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesBeganOrMovedWithTouches:touches]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesBeganOrMovedWithTouches:touches]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesEndedOrCancelledWithTouches:touches]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesEndedOrCancelledWithTouches:touches]; -} - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/JBChartView.h b/ios/PhyWeb/third-party/JBChartView/JBChartView.h deleted file mode 100644 index 8211f8ee..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBChartView.h +++ /dev/null @@ -1,170 +0,0 @@ -// -// JBChartView.h -// JBChartView -// -// Created by Terry Worona on 9/4/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -#import -#import - -extern CGFloat const kJBChartViewDefaultAnimationDuration; - -@class JBChartView; - -/** - * At a minimum, a chart can support two states, along with animations to-and-from. - */ -typedef NS_ENUM(NSInteger, JBChartViewState){ - /** - * Expanded state: chart supports touches, interaction, etc. - */ - JBChartViewStateExpanded, - /** - * Collapse state: chart is more-or-less disabled at this point. - */ - JBChartViewStateCollapsed -}; - -@protocol JBChartViewDataSource - -@optional - -/** - * Returns whether or not the chart's selection view should extend into the header padding. - * - * Default: NO - * - * @param chartView The chart object requesting this information. - * - * @return Whether or not a chart's selection view should extend into the header padding. - */ -- (BOOL)shouldExtendSelectionViewIntoHeaderPaddingForChartView:(JBChartView *)chartView; - -/** - * Returns whether or not the chart's selection view should extend into the footer padding. - * - * Default: NO - * - * @param chartView The chart object requesting this information. - * - * @return Whether or not a chart's selection view should extend into the footer padding. - */ -- (BOOL)shouldExtendSelectionViewIntoFooterPaddingForChartView:(JBChartView *)chartView; - -@end - -@protocol JBChartViewDelegate - -// Extend (via subclass) to add custom functionality - -@end - -@interface JBChartView : UIView - -/* - * Base dataSource and delegate protocols. - */ -@property (nonatomic, weak) id dataSource; -@property (nonatomic, weak) id delegate; - -/** - * Header and footer views are shown above and below the chart respectively. - * Each view will be stretched horizontally to fill width of chart. - * Each view's bounds are clipped to support chart state animations. - */ -@property (nonatomic, strong) UIView *footerView; -@property (nonatomic, strong) UIView *headerView; - -/** - * The vertical padding between the header and highest chart point (bar, line, etc). - * By default, the selection view will extend into the header padding area. To modify this behaviour, - * implement the dataSource protocol - (BOOL)shouldExtendSelectionViewIntoHeaderPaddingForChartView:(JBChartView *)chartView. - */ -@property (nonatomic, assign) CGFloat headerPadding; - -/** - * The vertical padding between the footer and lowest chart point (bar, line, etc). - * By default, the selection view will extend into the footer padding area. To modify this behaviour, - * implement the dataSource protocol - (BOOL)shouldExtendSelectionViewIntoFooterPaddingForChartView:(JBChartView *)chartView. - */ -@property (nonatomic, assign) CGFloat footerPadding; - -/** - * The minimum and maxmimum values of the chart. - * If no value(s) are supplied: - * - * minimumValue = chart's data source min value. - * maxmimumValue = chart's data source max value. - * - * If value(s) are supplied, they must be >= 0, otherwise an assertion will be thrown. - * The min/max values are clamped to the ceiling and floor of the actual min/max values of the chart's data source; - * for example, if a maximumValue of 20 is supplied & the chart's actual max is 100, then 100 will be used. - * - * For min/max modifications to take effect, reloadData must be called. - */ -@property (nonatomic, assign) CGFloat minimumValue; -@property (nonatomic, assign) CGFloat maximumValue; - -// reset to default (chart's data source min & max value) -- (void)resetMinimumValue; -- (void)resetMaximumValue; - -/** - * Charts can either be expanded or contracted. - * By default, a chart should be expanded on initialization. - */ -@property (nonatomic, assign) JBChartViewState state; - -/** - * Acts similiar to a UITableView's reloadData function. - * The entire chart will be torn down and re-constructed via datasource and delegate protocls. - * If a chart's frame changes, reloadData must be called directly afterwards for the changes to take effect. - */ -- (void)reloadData; - -/** - * State setter. - * - * @param state Either collapse or expanded. - * @param animated Whether or not the state should be animated or not. - * @param force If current state == new state, then setting force to YES will re-configure the chart (default NO). - * @param callback Called once the animation is completed. If animated == NO, then callback is immediate. - */ -- (void)setState:(JBChartViewState)state animated:(BOOL)animated force:(BOOL)force callback:(void (^)())callback; - -/** - * State setter. - * - * @param state Either collapse or expanded. - * @param animated Whether or not the state should be animated or not. - * @param callback Called once the animation is completed. If animated == NO, then callback is immediate. - */ -- (void)setState:(JBChartViewState)state animated:(BOOL)animated callback:(void (^)())callback; - -/** - * State setter. - * - * @param state Either collapse or expanded. - * @param animated Whether or not the state should be animated or not. - */ -- (void)setState:(JBChartViewState)state animated:(BOOL)animated; - -@end - -/** - * A simple UIView subclass that fades a base color from current alpha to 0.0 (vertically). - * Used as a vertical selection view in JBChartView subclasses. - */ -@interface JBChartVerticalSelectionView : UIView - -/** - * Base selection view color. This color will be faded to transparent vertically. - * - * Default: white color. - * - */ -@property (nonatomic, strong) UIColor *bgColor; - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/JBChartView.m b/ios/PhyWeb/third-party/JBChartView/JBChartView.m deleted file mode 100644 index 6589eb8c..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBChartView.m +++ /dev/null @@ -1,235 +0,0 @@ -// -// JBChartView.m -// Nudge -// -// Created by Terry Worona on 9/4/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -#import "JBChartView.h" - -// Numerics -CGFloat const kJBChartViewDefaultAnimationDuration = 0.25f; - -// Color (JBChartSelectionView) -static UIColor *kJBChartVerticalSelectionViewDefaultBgColor = nil; - -@interface JBChartView () - -@property (nonatomic, assign) BOOL hasMaximumValue; -@property (nonatomic, assign) BOOL hasMinimumValue; - -// Construction -- (void)constructChartView; - -// Validation -- (void)validateHeaderAndFooterHeights; - -@end - -@implementation JBChartView - -#pragma mark - Alloc/Init - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (self) - { - [self constructChartView]; - } - return self; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - [self constructChartView]; - } - return self; -} - -- (id)init -{ - return [self initWithFrame:CGRectZero]; -} - -#pragma mark - Construction - -- (void)constructChartView -{ - self.clipsToBounds = YES; -} - -#pragma mark - Public - -- (void)reloadData -{ - // Override -} - -#pragma mark - Validation - -- (void)validateHeaderAndFooterHeights -{ - NSAssert((self.headerView.bounds.size.height + self.footerView.bounds.size.height) <= self.bounds.size.height, @"JBChartView // the combined height of the footer and header can not be greater than the total height of the chart."); -} - -#pragma mark - Setters - -- (void)setHeaderView:(UIView *)headerView -{ - if (_headerView) - { - [_headerView removeFromSuperview]; - _headerView = nil; - } - _headerView = headerView; - _headerView.clipsToBounds = YES; - - [self validateHeaderAndFooterHeights]; - - [self addSubview:_headerView]; - [self reloadData]; -} - -- (void)setFooterView:(UIView *)footerView -{ - if (_footerView) - { - [_footerView removeFromSuperview]; - _footerView = nil; - } - _footerView = footerView; - _footerView.clipsToBounds = YES; - - [self validateHeaderAndFooterHeights]; - - [self addSubview:_footerView]; - [self reloadData]; -} - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated force:(BOOL)force callback:(void (^)())callback -{ - if ((_state == state) && !force) - { - return; - } - - _state = state; - - // Override -} - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated callback:(void (^)())callback -{ - [self setState:state animated:animated force:NO callback:callback]; -} - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated -{ - [self setState:state animated:animated callback:nil]; -} - -- (void)setState:(JBChartViewState)state -{ - [self setState:state animated:NO]; -} - -- (void)setMinimumValue:(CGFloat)minimumValue -{ - NSAssert(minimumValue >= 0, @"JBChartView // the minimumValue must be >= 0."); - _minimumValue = minimumValue; - _hasMinimumValue = YES; -} - -- (void)setMaximumValue:(CGFloat)maximumValue -{ - NSAssert(maximumValue >= 0, @"JBChartView // the maximumValue must be >= 0."); - _maximumValue = maximumValue; - _hasMaximumValue = YES; -} - -- (void)resetMinimumValue -{ - _hasMinimumValue = NO; // clears min -} - -- (void)resetMaximumValue -{ - _hasMaximumValue = NO; // clears max -} - -@end - -@implementation JBChartVerticalSelectionView - -#pragma mark - Alloc/Init - -+ (void)initialize -{ - if (self == [JBChartVerticalSelectionView class]) - { - kJBChartVerticalSelectionViewDefaultBgColor = [UIColor whiteColor]; - } -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - self.backgroundColor = [UIColor clearColor]; - } - return self; -} - -#pragma mark - Drawing - -- (void)drawRect:(CGRect)rect -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - [[UIColor clearColor] set]; - CGContextFillRect(context, rect); - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGFloat locations[] = { 0.0, 1.0 }; - - NSArray *colors = nil; - if (self.bgColor != nil) - { - colors = @[(__bridge id)self.bgColor.CGColor, (__bridge id)[self.bgColor colorWithAlphaComponent:0.0].CGColor]; - } - else - { - colors = @[(__bridge id)kJBChartVerticalSelectionViewDefaultBgColor.CGColor, (__bridge id)[kJBChartVerticalSelectionViewDefaultBgColor colorWithAlphaComponent:0.0].CGColor]; - } - - CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations); - - CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); - CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); - - CGContextSaveGState(context); - { - CGContextAddRect(context, rect); - CGContextClip(context); - CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); - } - CGContextRestoreGState(context); - - CGGradientRelease(gradient); - CGColorSpaceRelease(colorSpace); -} - -#pragma mark - Setters - -- (void)setBgColor:(UIColor *)bgColor -{ - _bgColor = bgColor; - [self setNeedsDisplay]; -} - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/JBLineChartView.h b/ios/PhyWeb/third-party/JBChartView/JBLineChartView.h deleted file mode 100755 index bcfb58e6..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBLineChartView.h +++ /dev/null @@ -1,332 +0,0 @@ -// -// JBLineChartView.h -// JBChartView -// -// Created by Terry Worona on 9/4/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -#import "JBChartView.h" - -@class JBLineChartView; - -/** - * Current support for two line styles: solid (default) and dashed. - */ -typedef NS_ENUM(NSInteger, JBLineChartViewLineStyle){ - /** - * Solid line. - */ - JBLineChartViewLineStyleSolid, - /** - * Dashed line with a phase of 3:2 (3 points dashed, 2 points spaced). - */ - JBLineChartViewLineStyleDashed -}; - -@protocol JBLineChartViewDataSource - -@required - -/** - * Returns the number of lines for the line chart. - * - * @param lineChartView The line chart object requesting this information. - * - * @return The number of lines in the line chart. - */ -- (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView; - -/** - * Returns the number of vertical values for a particular line at lineIndex within the chart. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The number of vertical values for a given line in the line chart. - */ -- (NSUInteger)lineChartView:(JBLineChartView *)lineChartView numberOfVerticalValuesAtLineIndex:(NSUInteger)lineIndex; - -@optional - -/** - * Returns whether or not a line should show a dot for each point. - * Dot size is relative to the line width and not adjustable. - * Dot color is equal to the line color and not adjustable. - * - * Default: NO. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return Whether or not a line should show a dot for each chart point. - */ -- (BOOL)lineChartView:(JBLineChartView *)lineChartView showsDotsForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns whether or not a line should be rendered with curved connections and rounded end caps. - * - * Default: NO. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return Whether or not a line should smooth it's connections and end caps. - */ -- (BOOL)lineChartView:(JBLineChartView *)lineChartView smoothLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns a (custom) UIView instance representing a dot (x,y point) within the chart. - * For this value to apply, showsDotsForLineAtLineIndex: must return YES for the line at lineIndex. - * This protocol supercedes colorForDotAtHorizontalIndex: and dotRadiusForDotAtHorizontalIndex:. - * If nil is returned. the original dot protocols will take precedence. During selection events, a custom - * dot view will not be hidden unless lineChartView:shouldHideDotViewOnSelectionAtHorizontalIndex:atLineIndex: - * is implemented. - * - * Default: nil. - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param lineIndex An index number identifying a line in the chart. - * - * @return A custom UIView instance representing a dot at a particular horizontal index within a dotted line. - */ -- (UIView *)lineChartView:(JBLineChartView *)lineChartView dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -/** - * Returns whether or not a (custom) dot view should be hidden on selection events. - * - * Default: NO. - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param lineIndex An index number identifying a line in the chart. - * - * @return Whether or not a (custom) dot view should be hidden on selection events. - */ -- (BOOL)lineChartView:(JBLineChartView *)lineChartView shouldHideDotViewOnSelectionAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -@end - -@protocol JBLineChartViewDelegate - -@required - -/** - * Vertical value for a line point at a given index (left to right). There is no ceiling on the the height; - * the chart will automatically normalize all values between the overal min and max heights. - * NAN may able be retuend to indicate missing values. The chart's line will begin at the first non-NAN value and end at the last non-NAN value. - * Furthermore, the line will interopolate any NAN values in between (ie. the line will not be interrupted). - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param lineIndex An index number identifying the closest line in the chart to the current touch point. - * - * @return The y-axis value of the supplied line index (x-axis) - */ -- (CGFloat)lineChartView:(JBLineChartView *)lineChartView verticalValueForHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -@optional - -/** - * Occurs whenever there is a touch gesture on the chart (chart must be expanded). - * The horizontal index is the closest index to the touch point & is clamped to it's max/min value if it moves outside of the view's bounds. - * The lineIndex remains constant until the line is deselected and will be highlighted using the (optional) selectionColorForLineAtLineIndex: protocol. - * Futhermore, all other lines that aren't selected will be dimmed to 20%% opacity throughout the duration of the touch/move. Any dotted line that isn't the - * primary selection will have it's dots dimmed to hidden (to avoid transparency issues). - * - * @param lineChartView A line chart object informing the delegate about the new selection. - * @param lineIndex An index number identifying the closest line in the chart to the current touch - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param touchPoint The touch point in relation to the chart's bounds (excludes footer and header). - */ -- (void)lineChartView:(JBLineChartView *)lineChartView didSelectLineAtIndex:(NSUInteger)lineIndex horizontalIndex:(NSUInteger)horizontalIndex touchPoint:(CGPoint)touchPoint; -- (void)lineChartView:(JBLineChartView *)lineChartView didSelectLineAtIndex:(NSUInteger)lineIndex horizontalIndex:(NSUInteger)horizontalIndex; - -/** - * Occurs when selection ends by ending a touch event. For selection start events, see: didSelectChartAtIndex: - * - * @param lineChartView A line chart object informing the delegate about the deselection. - */ -- (void)didDeselectLineInLineChartView:(JBLineChartView *)lineChartView; - -/** - * Returns the color of particular line at lineIndex within the chart. - * - * Default: black color. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color to be used to shade a line in the chart. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView colorForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the fill color of particular line at lineIndex within the chart. - * - * Default: clear color. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The fill color to show under a line in the chart. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView fillColorForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the color of a particular dot in a line at lineIndex within the chart. - * For this value to apply, showsDotsForLineAtLineIndex: must return YES for the line at lineIndex. - * Any value can be returned for lineIndex's that don't support dots, as it will never be called. - * - * Default: black color. - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis) - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color to be used to color a dot within a dotted line in the chart. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView colorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the width of particular line at lineIndex within the chart. - * - * Default: 5 points. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The width to be used to draw a line in the chart. - */ -- (CGFloat)lineChartView:(JBLineChartView *)lineChartView widthForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the radius of all dots in a particular line at lineIndex within the chart. - * For this value to apply, showsDotsForLineAtLineIndex: must return YES for the line at lineIndex. - * Any value can be returned for lineIndex's that don't support dots, as it will never be called. - * - * Default: line width x 6. - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param lineIndex An index number identifying a line in the chart. - * - * @return The radius of the dots within a dotted line in the chart. - */ -- (CGFloat)lineChartView:(JBLineChartView *)lineChartView dotRadiusForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the width of the (vertical) selection view to be overlayed on the chart during touch events. - * The property showsVerticalSelection must be YES for the width to apply. The width is clamped to the - * maxmimum width of the chart's bounds. - * - * Default: 20px. - * - * @param lineChartView The line chart object requesting this information. - * - * @return The width of the selection view used during chart selections. - */ -- (CGFloat)verticalSelectionWidthForLineChartView:(JBLineChartView *)lineChartView; - -/** - * Returns the (vertical) selection color to be overlayed on the chart during touch events on a given line. - * The color is automically faded to transparent (vertically). The property showsVerticalSelection - * must be YES for the color to apply. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color of the selection view used during chart selections of the given line. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView verticalSelectionColorForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the selection color to be overlayed on a line within the chart during touch events. - * The property showsLineSelection must be YES for the color to apply. - * - * Default: white color. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color to be used to highlight a line during chart selections. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView selectionColorForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the selection fill color to be overlayed under a line within the chart during touch events. - * The property showsLineSelection must be YES for the color to apply. - * - * Default: clear color. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color to be used to highlight under a line during chart selections. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView selectionFillColorForLineAtLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the selection color to be overlayed on a line within the chart during touch events. - * The property showsLineSelection must be YES for the color to apply. - * - * Default: white color. - * - * @param lineChartView The line chart object requesting this information. - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis). - * @param lineIndex An index number identifying a line in the chart. - * - * @return The color to be used to highlight a dot within a dotted line during chart selections. - */ -- (UIColor *)lineChartView:(JBLineChartView *)lineChartView selectionColorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -/** - * Returns the line style of a particular line at lineIndex within the chart. - * See JBLineChartViewLineStyle for line style descriptions. - * - * Default: JBLineChartViewLineStyleSolid. - * - * @param lineChartView The line chart object requesting this information. - * @param lineIndex An index number identifying a line in the chart. - * - * @return The line style to be used to draw a line in the chart. - */ -- (JBLineChartViewLineStyle)lineChartView:(JBLineChartView *)lineChartView lineStyleForLineAtLineIndex:(NSUInteger)lineIndex; - -@end - -@interface JBLineChartView : JBChartView - -@property (nonatomic, weak) id dataSource; -@property (nonatomic, weak) id delegate; - -/** - * Vertical highlight overlayed on a line graph during touch events. - * - * Default: YES. - */ -@property (nonatomic, assign) BOOL showsVerticalSelection; - -/** - * A highlight shown on a line within the graph during touch events. The highlighted line - * is the closest line to the touch point and corresponds to the lineIndex delegatd back via - * didSelectChartAtHorizontalIndex:atLineIndex: and didUnSlectChartAtHorizontalIndex:atLineIndex: - * - * Default: YES. - */ -@property (nonatomic, assign) BOOL showsLineSelection; - -/** - * The dot view within a particular line at a horizontalIndex. - * - * Default: nil. - * - * @param horizontalIndex The 0-based horizontal index of a selection point (left to right, x-axis) - * @param lineIndex An index number identifying a line in the chart. - * - * @return The UIView representing the dot view at a given horizontalIndex within a line or nil if any index is out of range. - */ -- (UIView *)dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/JBLineChartView.m b/ios/PhyWeb/third-party/JBChartView/JBLineChartView.m deleted file mode 100755 index e4dd01bb..00000000 --- a/ios/PhyWeb/third-party/JBChartView/JBLineChartView.m +++ /dev/null @@ -1,1620 +0,0 @@ -// -// JBLineChartView.m -// Nudge -// -// Created by Terry Worona on 9/4/13. -// Copyright (c) 2013 Jawbone. All rights reserved. -// - -#import "JBLineChartView.h" - -// Drawing -#import - -// Enums -typedef NS_ENUM(NSUInteger, JBLineChartHorizontalIndexClamp){ - JBLineChartHorizontalIndexClampLeft, - JBLineChartHorizontalIndexClampRight, - JBLineChartHorizontalIndexClampNone -}; - -// Numerics (JBLineChartLineView) -CGFloat static const kJBLineChartLinesViewStrokeWidth = 5.0; -CGFloat static const kJBLineChartLinesViewMiterLimit = -5.0; -CGFloat static const kJBLineChartLinesViewDefaultLinePhase = 1.0f; -CGFloat static const kJBLineChartLinesViewDefaultDimmedOpacity = 0.20f; -NSInteger static const kJBLineChartLinesViewUnselectedLineIndex = -1; -CGFloat static const kJBLineChartLinesViewSmoothThresholdSlope = 0.01f; -NSInteger static const kJBLineChartLinesViewSmoothThresholdVertical = 1; - -// Numerics (JBLineChartDotsView) -NSInteger static const kJBLineChartDotsViewDefaultRadiusFactor = 3; // 3x size of line width -NSInteger static const kJBLineChartDotsViewUnselectedLineIndex = -1; - -// Numerics (JBLineSelectionView) -CGFloat static const kJBLineSelectionViewWidth = 20.0f; - -// Numerics (JBLineChartView) -CGFloat static const kJBBarChartViewUndefinedCachedHeight = -1.0f; -CGFloat static const kJBLineChartViewStateAnimationDuration = 0.25f; -CGFloat static const kJBLineChartViewStateAnimationDelay = 0.05f; -CGFloat static const kJBLineChartViewStateBounceOffset = 15.0f; -NSInteger static const kJBLineChartUnselectedLineIndex = -1; - -// Collections (JBLineChartLineView) -static NSArray *kJBLineChartLineViewDefaultDashPattern = nil; - -// Colors (JBLineChartView) -static UIColor *kJBLineChartViewDefaultLineColor = nil; -static UIColor *kJBLineChartViewDefaultLineFillColor = nil; -static UIColor *kJBLineChartViewDefaultLineSelectionColor = nil; -static UIColor *kJBLineChartViewDefaultLineSelectionFillColor = nil; -static UIColor *kJBLineChartViewDefaultDotColor = nil; -static UIColor *kJBLineChartViewDefaultDotSelectionColor = nil; - -@interface JBChartView (Private) - -- (BOOL)hasMaximumValue; -- (BOOL)hasMinimumValue; - -@end - -@interface JBLineLayer : CAShapeLayer - -@property (nonatomic, assign) NSUInteger tag; -@property (nonatomic, assign) JBLineChartViewLineStyle lineStyle; - -@end - -@interface JBFillLayer : CAShapeLayer - -@property (nonatomic, assign) NSUInteger tag; - -@end - -@interface JBLineChartPoint : NSObject - -@property (nonatomic, assign) CGPoint position; -@property (nonatomic, assign) BOOL hidden; - -@end - -@protocol JBLineChartLinesViewDelegate; - -@interface JBLineChartLinesView : UIView - -@property (nonatomic, assign) id delegate; -@property (nonatomic, assign) NSInteger selectedLineIndex; // -1 to unselect -@property (nonatomic, assign) BOOL animated; - -// Data -- (void)reloadData; - -// Setters -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated; - -// Callback helpers -- (void)fireCallback:(void (^)())callback; - -// View helpers -- (JBLineLayer *)lineLayerForLineIndex:(NSUInteger)lineIndex; -- (JBFillLayer *)fillLayerForLineIndex:(NSUInteger)lineIndex; - -@end - -@protocol JBLineChartLinesViewDelegate - -- (NSArray *)chartDataForLineChartLinesView:(JBLineChartLinesView*)lineChartLinesView; -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex; -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedColorForLineAtLineIndex:(NSUInteger)lineIndex; -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex; -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedFillColorForLineAtLineIndex:(NSUInteger)lineIndex; -- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex; -- (CGFloat)paddingForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView; -- (JBLineChartViewLineStyle)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView lineStyleForLineAtLineIndex:(NSUInteger)lineIndex; -- (BOOL)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView smoothLineAtLineIndex:(NSUInteger)lineIndex; - -@end - -@protocol JBLineChartDotsViewDelegate; - -@interface JBLineChartDotsView : UIView // JBLineChartViewLineStyleDotted - -@property (nonatomic, assign) id delegate; -@property (nonatomic, assign) NSInteger selectedLineIndex; // -1 to unselect -@property (nonatomic, strong) NSDictionary *dotViewsDict; - -// Data -- (void)reloadData; - -// Setters -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated; - -@end - -@protocol JBLineChartDotsViewDelegate - -- (NSArray *)chartDataForLineChartDotsView:(JBLineChartDotsView*)lineChartDotsView; -- (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView colorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; -- (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView selectedColorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; -- (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView widthForLineAtLineIndex:(NSUInteger)lineIndex; -- (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotRadiusForLineAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; -- (UIView *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; -- (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView shouldHideDotViewOnSelectionAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex; -- (CGFloat)paddingForLineChartDotsView:(JBLineChartDotsView *)lineChartDotsView; -- (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView showsDotsForLineAtLineIndex:(NSUInteger)lineIndex; - -@end - -@interface JBLineChartDotView : UIView - -- (id)initWithRadius:(CGFloat)radius; - -@end - -@interface JBLineChartView () - -@property (nonatomic, strong) NSArray *chartData; -@property (nonatomic, strong) JBLineChartLinesView *linesView; -@property (nonatomic, strong) JBLineChartDotsView *dotsView; -@property (nonatomic, strong) JBChartVerticalSelectionView *verticalSelectionView; -@property (nonatomic, assign) CGFloat cachedMaxHeight; -@property (nonatomic, assign) CGFloat cachedMinHeight; -@property (nonatomic, assign) BOOL verticalSelectionViewVisible; - -// Initialization -- (void)construct; - -// View quick accessors -- (CGFloat)normalizedHeightForRawHeight:(CGFloat)rawHeight; -- (CGFloat)availableHeight; -- (CGFloat)padding; -- (NSUInteger)dataCount; - -// Touch helpers -- (CGPoint)clampPoint:(CGPoint)point toBounds:(CGRect)bounds padding:(CGFloat)padding; -- (NSInteger)horizontalIndexForPoint:(CGPoint)point indexClamp:(JBLineChartHorizontalIndexClamp)indexClamp lineData:(NSArray *)lineData; -- (NSInteger)horizontalIndexForPoint:(CGPoint)point indexClamp:(JBLineChartHorizontalIndexClamp)indexClamp; // uses largest line data -- (NSInteger)horizontalIndexForPoint:(CGPoint)point; -- (NSInteger)lineIndexForPoint:(CGPoint)point; -- (void)touchesBeganOrMovedWithTouches:(NSSet *)touches; -- (void)touchesEndedOrCancelledWithTouches:(NSSet *)touches; - -// Setters -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible animated:(BOOL)animated; - -@end - -@implementation JBLineChartView - -@dynamic dataSource; -@dynamic delegate; - -#pragma mark - Alloc/Init - -+ (void)initialize -{ - if (self == [JBLineChartView class]) - { - kJBLineChartViewDefaultLineColor = [UIColor blackColor]; - kJBLineChartViewDefaultLineFillColor = [UIColor clearColor]; - kJBLineChartViewDefaultLineSelectionColor = [UIColor whiteColor]; - kJBLineChartViewDefaultLineSelectionFillColor = [UIColor clearColor]; - kJBLineChartViewDefaultDotColor = [UIColor blackColor]; - kJBLineChartViewDefaultDotSelectionColor = [UIColor whiteColor]; - } -} - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (self) - { - [self construct]; - } - return self; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - [self construct]; - } - return self; -} - -- (id)init -{ - self = [super init]; - if (self) - { - [self construct]; - } - return self; -} - -- (void)construct -{ - _showsVerticalSelection = YES; - _showsLineSelection = YES; - _cachedMinHeight = kJBBarChartViewUndefinedCachedHeight; - _cachedMaxHeight = kJBBarChartViewUndefinedCachedHeight; -} - -#pragma mark - Data - -- (void)reloadData -{ - // Reset cached max height - self.cachedMinHeight = kJBBarChartViewUndefinedCachedHeight; - self.cachedMaxHeight = kJBBarChartViewUndefinedCachedHeight; - - // Padding - CGFloat chartPadding = [self padding]; - - /* - * Subview rectangle calculations - */ - CGRect mainViewRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, [self availableHeight]); - - /* - * The data collection holds all position and marker information: - * constructed via datasource and delegate functions - */ - dispatch_block_t createChartData = ^{ - - CGFloat pointSpace = (self.bounds.size.width - (chartPadding * 2)) / ([self dataCount] - 1); // Space in between points - CGFloat xOffset = chartPadding; - CGFloat yOffset = 0; - - NSMutableArray *mutableChartData = [NSMutableArray array]; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSUInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - for (NSUInteger lineIndex=0; lineIndex= 0), @"JBLineChartView // delegate function - (CGFloat)lineChartView:(JBLineChartView *)lineChartView verticalValueForHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex must return a CGFloat >= 0 OR NAN"); - - JBLineChartPoint *chartPoint = [[JBLineChartPoint alloc] init]; - if(isnan(rawHeight)) - { - chartPoint.hidden = YES; - rawHeight = 0; //set to 0 so we can calculate the x position - } - - CGFloat normalizedHeight = [self normalizedHeightForRawHeight:rawHeight]; - yOffset = mainViewRect.size.height - normalizedHeight; - - chartPoint.position = CGPointMake(xOffset, yOffset); - - [chartPointData addObject:chartPoint]; - xOffset += pointSpace; - } - [mutableChartData addObject:chartPointData]; - xOffset = chartPadding; - } - self.chartData = [NSArray arrayWithArray:mutableChartData]; - }; - - /* - * Creates a new line graph view using the previously calculated data model - */ - dispatch_block_t createLineGraphView = ^{ - - // Remove old line view - if (self.linesView) - { - self.linesView.delegate = nil; - [self.linesView removeFromSuperview]; - self.linesView = nil; - } - - // Create new line and overlay subviews - self.linesView = [[JBLineChartLinesView alloc] initWithFrame:CGRectOffset(mainViewRect, 0, self.headerView.frame.size.height + self.headerPadding)]; - self.linesView.delegate = self; - - // Add new lines view - if (self.footerView) - { - [self insertSubview:self.linesView belowSubview:self.footerView]; - } - else - { - [self addSubview:self.linesView]; - } - }; - - /* - * Creates a new dot graph view using the previously calculated data model - */ - dispatch_block_t createDotGraphView = ^{ - - // Remove old dot view - if (self.dotsView) - { - self.dotsView.delegate = nil; - [self.dotsView removeFromSuperview]; - self.dotsView = nil; - } - - // Create new line and overlay subviews - self.dotsView = [[JBLineChartDotsView alloc] initWithFrame:CGRectOffset(mainViewRect, 0, self.headerView.frame.size.height + self.headerPadding)]; - self.dotsView.delegate = self; - - // Add new dots view - if (self.footerView) - { - [self insertSubview:self.dotsView belowSubview:self.footerView]; - } - else - { - [self addSubview:self.dotsView]; - } - }; - - /* - * Creates a vertical selection view for touch events - */ - dispatch_block_t createSelectionView = ^{ - if (self.verticalSelectionView) - { - [self.verticalSelectionView removeFromSuperview]; - self.verticalSelectionView = nil; - } - - CGFloat selectionViewWidth = kJBLineSelectionViewWidth; - if ([self.delegate respondsToSelector:@selector(verticalSelectionWidthForLineChartView:)]) - { - selectionViewWidth = MIN([self.delegate verticalSelectionWidthForLineChartView:self], self.bounds.size.width); - } - - CGFloat verticalSelectionViewHeight = self.bounds.size.height - self.headerView.frame.size.height - self.footerView.frame.size.height - self.headerPadding - self.footerPadding; - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoHeaderPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoHeaderPaddingForChartView:self]) - { - verticalSelectionViewHeight += self.headerPadding; - } - } - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoFooterPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoFooterPaddingForChartView:self]) - { - verticalSelectionViewHeight += self.footerPadding; - } - } - - self.verticalSelectionView = [[JBChartVerticalSelectionView alloc] initWithFrame:CGRectMake(0, 0, selectionViewWidth, verticalSelectionViewHeight)]; - self.verticalSelectionView.alpha = 0.0; - self.verticalSelectionView.hidden = !self.showsVerticalSelection; - - // Add new selection bar - if (self.footerView) - { - [self insertSubview:self.verticalSelectionView belowSubview:self.footerView]; - } - else - { - [self addSubview:self.verticalSelectionView]; - } - }; - - createChartData(); - createLineGraphView(); - createDotGraphView(); - createSelectionView(); - - // Reload views - [self.linesView reloadData]; - [self.dotsView reloadData]; - - // Position header and footer - self.headerView.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.headerView.frame.size.height); - self.footerView.frame = CGRectMake(self.bounds.origin.x, self.bounds.size.height - self.footerView.frame.size.height, self.bounds.size.width, self.footerView.frame.size.height); - - // Refresh state - [self setState:self.state animated:NO force:YES callback:nil]; -} - -#pragma mark - View Quick Accessors - -- (CGFloat)normalizedHeightForRawHeight:(CGFloat)rawHeight -{ - CGFloat minHeight = [self minimumValue]; - CGFloat maxHeight = [self maximumValue]; - - if ((maxHeight - minHeight) <= 0) - { - return 0; - } - - return ((rawHeight - minHeight) / (maxHeight - minHeight)) * [self availableHeight]; -} - -- (CGFloat)availableHeight -{ - return self.bounds.size.height - self.headerView.frame.size.height - self.footerView.frame.size.height - self.headerPadding - self.footerPadding; -} - -- (CGFloat)padding -{ - CGFloat maxLineWidth = 0.0f; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - - for (NSInteger lineIndex=0; lineIndex maxDotLength || customDotView.frame.size.height > maxDotLength) - { - maxDotLength = fmaxf(customDotView.frame.size.width, customDotView.frame.size.height); - } - } - } - else if ([self.delegate respondsToSelector:@selector(lineChartView:dotRadiusForDotAtHorizontalIndex:atLineIndex:)]) - { - CGFloat dotRadius = ([self.delegate lineChartView:self dotRadiusForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex] * 2.0f); - if (dotRadius > maxDotLength) - { - maxDotLength = dotRadius; - } - } - else - { - CGFloat defaultDotRadius = ((lineWidth * kJBLineChartDotsViewDefaultRadiusFactor) * 2.0f); - if (defaultDotRadius > maxDotLength) - { - maxDotLength = defaultDotRadius; - } - } - } - } - } - - CGFloat currentMaxLineWidth = MAX(maxDotLength, lineWidth); - if (currentMaxLineWidth > maxLineWidth) - { - maxLineWidth = currentMaxLineWidth; - } - } - return ceil(maxLineWidth * 0.5); -} - -- (NSUInteger)dataCount -{ - NSUInteger dataCount = 0; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - for (NSInteger lineIndex=0; lineIndex dataCount) - { - dataCount = lineDataCount; - } - } - return dataCount; -} - -#pragma mark - JBLineChartLinesViewDelegate - -- (NSArray *)chartDataForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView -{ - return self.chartData; -} - -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:colorForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self colorForLineAtLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultLineColor; -} - -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedColorForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:selectionColorForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self selectionColorForLineAtLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultLineSelectionColor; -} - -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:fillColorForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self fillColorForLineAtLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultLineFillColor; -} - -- (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedFillColorForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:selectionFillColorForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self selectionFillColorForLineAtLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultLineSelectionFillColor; -} - -- (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:widthForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self widthForLineAtLineIndex:lineIndex]; - } - return kJBLineChartLinesViewStrokeWidth; -} - -- (CGFloat)paddingForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView -{ - return [self padding]; -} - -- (JBLineChartViewLineStyle)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView lineStyleForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:lineStyleForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self lineStyleForLineAtLineIndex:lineIndex]; - } - return JBLineChartViewLineStyleSolid; -} - -- (BOOL)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView smoothLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.dataSource respondsToSelector:@selector(lineChartView:smoothLineAtLineIndex:)]) - { - return [self.dataSource lineChartView:self smoothLineAtLineIndex:lineIndex]; - } - return NO; -} - -#pragma mark - JBLineChartDotsViewDelegate - -- (NSArray *)chartDataForLineChartDotsView:(JBLineChartDotsView*)lineChartDotsView -{ - return self.chartData; -} - -- (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView colorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:colorForDotAtHorizontalIndex:atLineIndex:)]) - { - return [self.delegate lineChartView:self colorForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultDotColor; -} - -- (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView selectedColorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:selectionColorForDotAtHorizontalIndex:atLineIndex:)]) - { - return [self.delegate lineChartView:self selectionColorForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - return kJBLineChartViewDefaultDotSelectionColor; -} - -- (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView widthForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:widthForLineAtLineIndex:)]) - { - return [self.delegate lineChartView:self widthForLineAtLineIndex:lineIndex]; - } - return kJBLineChartLinesViewStrokeWidth; -} - -- (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotRadiusForLineAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - if ([self.delegate respondsToSelector:@selector(lineChartView:dotRadiusForDotAtHorizontalIndex:atLineIndex:)]) - { - return [self.delegate lineChartView:self dotRadiusForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - return [self lineChartDotsView:lineChartDotsView widthForLineAtLineIndex:lineIndex] * kJBLineChartDotsViewDefaultRadiusFactor; -} - -- (UIView *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - if ([self.dataSource respondsToSelector:@selector(lineChartView:dotViewAtHorizontalIndex:atLineIndex:)]) - { - return [self.dataSource lineChartView:self dotViewAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - return nil; -} - -- (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView shouldHideDotViewOnSelectionAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - if ([self.dataSource respondsToSelector:@selector(lineChartView:shouldHideDotViewOnSelectionAtHorizontalIndex:atLineIndex:)]) - { - return [self.dataSource lineChartView:self shouldHideDotViewOnSelectionAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - return NO; -} - -- (CGFloat)paddingForLineChartDotsView:(JBLineChartDotsView *)lineChartDotsView -{ - return [self padding]; -} - -- (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView showsDotsForLineAtLineIndex:(NSUInteger)lineIndex -{ - if ([self.dataSource respondsToSelector:@selector(lineChartView:showsDotsForLineAtLineIndex:)]) - { - return [self.dataSource lineChartView:self showsDotsForLineAtLineIndex:lineIndex]; - } - return NO; -} - -#pragma mark - Setters - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated force:(BOOL)force callback:(void (^)())callback -{ - [super setState:state animated:animated force:force callback:callback]; - - if ([self.chartData count] > 0) - { - CGRect mainViewRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, [self availableHeight]); - CGFloat yOffset = self.headerView.frame.size.height + self.headerPadding; - - dispatch_block_t adjustViewFrames = ^{ - self.linesView.frame = CGRectMake(self.linesView.frame.origin.x, yOffset + ((self.state == JBChartViewStateCollapsed) ? (self.linesView.frame.size.height + self.footerView.frame.size.height) : 0.0), self.linesView.frame.size.width, self.linesView.frame.size.height); - self.dotsView.frame = CGRectMake(self.dotsView.frame.origin.x, yOffset + ((self.state == JBChartViewStateCollapsed) ? (self.dotsView.frame.size.height + self.footerView.frame.size.height) : 0.0), self.dotsView.frame.size.width, self.dotsView.frame.size.height); - }; - - dispatch_block_t adjustViewAlphas = ^{ - self.linesView.alpha = (self.state == JBChartViewStateExpanded) ? 1.0 : 0.0; - self.dotsView.alpha = (self.state == JBChartViewStateExpanded) ? 1.0 : 0.0; - }; - - if (animated) - { - [UIView animateWithDuration:(kJBLineChartViewStateAnimationDuration * 0.5) delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.linesView.frame = CGRectOffset(mainViewRect, 0, yOffset - kJBLineChartViewStateBounceOffset); // bounce - self.dotsView.frame = CGRectOffset(mainViewRect, 0, yOffset - kJBLineChartViewStateBounceOffset); - } completion:^(BOOL finished) { - [UIView animateWithDuration:kJBLineChartViewStateAnimationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - adjustViewFrames(); - } completion:^(BOOL adjustFinished) { - if (callback) - { - callback(); - } - }]; - }]; - [UIView animateWithDuration:kJBLineChartViewStateAnimationDuration delay:(self.state == JBChartViewStateExpanded) ? kJBLineChartViewStateAnimationDelay : 0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - adjustViewAlphas(); - } completion:nil]; - } - else - { - adjustViewAlphas(); - adjustViewFrames(); - if (callback) - { - callback(); - } - } - } - else - { - if (callback) - { - callback(); - } - } -} - -- (void)setState:(JBChartViewState)state animated:(BOOL)animated callback:(void (^)())callback -{ - [self setState:state animated:animated force:NO callback:callback]; -} - -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible animated:(BOOL)animated -{ - _verticalSelectionViewVisible = verticalSelectionViewVisible; - - if (animated) - { - [UIView animateWithDuration:kJBChartViewDefaultAnimationDuration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.verticalSelectionView.alpha = self.verticalSelectionViewVisible ? 1.0 : 0.0; - } completion:nil]; - } - else - { - self.verticalSelectionView.alpha = _verticalSelectionViewVisible ? 1.0 : 0.0; - } -} - -- (void)setVerticalSelectionViewVisible:(BOOL)verticalSelectionViewVisible -{ - [self setVerticalSelectionViewVisible:verticalSelectionViewVisible animated:NO]; -} - -- (void)setShowsVerticalSelection:(BOOL)showsVerticalSelection -{ - _showsVerticalSelection = showsVerticalSelection; - self.verticalSelectionView.hidden = _showsVerticalSelection ? NO : YES; -} - -#pragma mark - Getters - -- (CGFloat)cachedMinHeight -{ - if (_cachedMinHeight == kJBBarChartViewUndefinedCachedHeight) - { - CGFloat minHeight = FLT_MAX; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSUInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - for (NSUInteger lineIndex=0; lineIndex= 0), @"JBLineChartView // delegate function - (CGFloat)lineChartView:(JBLineChartView *)lineChartView verticalValueForHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex must return a CGFloat >= 0 OR NAN"); - if (!isnan(height) && height < minHeight) - { - minHeight = height; - } - } - } - _cachedMinHeight = minHeight; - } - return _cachedMinHeight; -} - -- (CGFloat)cachedMaxHeight -{ - if (_cachedMaxHeight == kJBBarChartViewUndefinedCachedHeight) - { - CGFloat maxHeight = 0; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSUInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - for (NSUInteger lineIndex=0; lineIndex= 0), @"JBLineChartView // delegate function - (CGFloat)lineChartView:(JBLineChartView *)lineChartView verticalValueForHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex must return a CGFloat >= 0 OR NAN"); - if (!isnan(height) && height > maxHeight) - { - maxHeight = height; - } - } - } - _cachedMaxHeight = maxHeight; - } - return _cachedMaxHeight; -} - -- (CGFloat)minimumValue -{ - if ([self hasMinimumValue]) - { - return [super minimumValue]; - } - return self.cachedMinHeight; -} - -- (CGFloat)maximumValue -{ - if ([self hasMaximumValue]) - { - return [super maximumValue]; - } - return self.cachedMaxHeight; -} - -- (UIView *)dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex -{ - NSArray *dotViews = [self.dotsView.dotViewsDict objectForKey:@(lineIndex)]; - if (horizontalIndex < [dotViews count]) - { - return [dotViews objectAtIndex:horizontalIndex]; - } - return nil; -} - -#pragma mark - Touch Helpers - -- (CGPoint)clampPoint:(CGPoint)point toBounds:(CGRect)bounds padding:(CGFloat)padding -{ - return CGPointMake(MIN(MAX(bounds.origin.x + padding, point.x), bounds.size.width - padding), - MIN(MAX(bounds.origin.y + padding, point.y), bounds.size.height - padding)); -} - -- (NSInteger)horizontalIndexForPoint:(CGPoint)point indexClamp:(JBLineChartHorizontalIndexClamp)indexClamp lineData:(NSArray *)lineData -{ - NSUInteger index = 0; - CGFloat currentDistance = INT_MAX; - NSInteger selectedIndex = kJBLineChartUnselectedLineIndex; - - for (JBLineChartPoint *lineChartPoint in lineData) - { - BOOL clamped = (indexClamp == JBLineChartHorizontalIndexClampNone) ? YES : (indexClamp == JBLineChartHorizontalIndexClampLeft) ? (point.x - lineChartPoint.position.x >= 0) : (point.x - lineChartPoint.position.x <= 0); - if ((fabs(point.x - lineChartPoint.position.x)) < currentDistance && clamped == YES) - { - currentDistance = (fabs(point.x - lineChartPoint.position.x)); - selectedIndex = index; - } - index++; - } - - return selectedIndex != kJBLineChartUnselectedLineIndex ? selectedIndex : [lineData count] - 1; -} - -- (NSInteger)horizontalIndexForPoint:(CGPoint)point indexClamp:(JBLineChartHorizontalIndexClamp)indexClamp -{ - NSArray *largestLineData = nil; - for (NSArray *lineData in self.chartData) - { - if ([lineData count] > [largestLineData count]) - { - largestLineData = lineData; - } - } - return [self horizontalIndexForPoint:point indexClamp:indexClamp lineData:largestLineData]; -} - -- (NSInteger)horizontalIndexForPoint:(CGPoint)point -{ - return [self horizontalIndexForPoint:point indexClamp:JBLineChartHorizontalIndexClampNone]; -} - -- (NSInteger)lineIndexForPoint:(CGPoint)point -{ - // Find the horizontal indexes - NSUInteger leftHorizontalIndex = [self horizontalIndexForPoint:point indexClamp:JBLineChartHorizontalIndexClampLeft]; - NSUInteger rightHorizontalIndex = [self horizontalIndexForPoint:point indexClamp:JBLineChartHorizontalIndexClampRight]; - - // Padding - CGFloat chartPadding = [self padding]; - - NSUInteger shortestDistance = INT_MAX; - NSInteger selectedIndex = kJBLineChartUnselectedLineIndex; - NSAssert([self.dataSource respondsToSelector:@selector(numberOfLinesInLineChartView:)], @"JBLineChartView // dataSource must implement - (NSUInteger)numberOfLinesInLineChartView:(JBLineChartView *)lineChartView"); - NSUInteger numberOfLines = [self.dataSource numberOfLinesInLineChartView:self]; - - // Iterate all lines - for (NSUInteger lineIndex=0; lineIndex rightHorizontalIndex) - { - NSArray *lineData = [self.chartData objectAtIndex:lineIndex]; - - // Left point - JBLineChartPoint *leftLineChartPoint = [lineData objectAtIndex:leftHorizontalIndex]; - CGPoint leftPoint = CGPointMake(leftLineChartPoint.position.x, fmin(fmax(chartPadding, self.linesView.bounds.size.height - leftLineChartPoint.position.y), self.linesView.bounds.size.height - chartPadding)); - - // Right point - JBLineChartPoint *rightLineChartPoint = [lineData objectAtIndex:rightHorizontalIndex]; - CGPoint rightPoint = CGPointMake(rightLineChartPoint.position.x, fmin(fmax(chartPadding, self.linesView.bounds.size.height - rightLineChartPoint.position.y), self.linesView.bounds.size.height - chartPadding)); - - // Touch point - CGPoint normalizedTouchPoint = CGPointMake(point.x, self.linesView.bounds.size.height - point.y); - - // Slope - CGFloat lineSlope = (CGFloat)(rightPoint.y - leftPoint.y) / (CGFloat)(rightPoint.x - leftPoint.x); - - // Insersection point - CGPoint interesectionPoint = CGPointMake(normalizedTouchPoint.x, (lineSlope * (normalizedTouchPoint.x - leftPoint.x)) + leftPoint.y); - - CGFloat currentDistance = fabs(interesectionPoint.y - normalizedTouchPoint.y); - if (currentDistance < shortestDistance) - { - shortestDistance = currentDistance; - selectedIndex = lineIndex; - } - } - } - return selectedIndex; -} - -- (void)touchesBeganOrMovedWithTouches:(NSSet *)touches -{ - if (self.state == JBChartViewStateCollapsed || [self.chartData count] <= 0) - { - return; // no touch for no data or collapsed - } - - UITouch *touch = [touches anyObject]; - CGPoint touchPoint = [self clampPoint:[touch locationInView:self.linesView] toBounds:self.linesView.bounds padding:[self padding]]; - - NSUInteger lineIndex = self.linesView.selectedLineIndex != kJBLineChartLinesViewUnselectedLineIndex ? self.linesView.selectedLineIndex : [self lineIndexForPoint:touchPoint]; - - if (lineIndex == kJBLineChartLinesViewUnselectedLineIndex || [[self.chartData objectAtIndex:lineIndex] count] <= 0) - { - return; // no touch for line without data - } - - if ([self.delegate respondsToSelector:@selector(lineChartView:didSelectLineAtIndex:horizontalIndex:touchPoint:)]) - { - NSUInteger horizontalIndex = [self horizontalIndexForPoint:touchPoint indexClamp:JBLineChartHorizontalIndexClampNone lineData:[self.chartData objectAtIndex:lineIndex]]; - [self.delegate lineChartView:self didSelectLineAtIndex:lineIndex horizontalIndex:horizontalIndex touchPoint:[touch locationInView:self]]; - } - - if ([self.delegate respondsToSelector:@selector(lineChartView:didSelectLineAtIndex:horizontalIndex:)]) - { - [self.delegate lineChartView:self didSelectLineAtIndex:lineIndex horizontalIndex:[self horizontalIndexForPoint:touchPoint indexClamp:JBLineChartHorizontalIndexClampNone lineData:[self.chartData objectAtIndex:lineIndex]]]; - } - - if ([self.delegate respondsToSelector:@selector(lineChartView:verticalSelectionColorForLineAtLineIndex:)]) - { - UIColor *verticalSelectionColor = [self.delegate lineChartView:self verticalSelectionColorForLineAtLineIndex:lineIndex]; - NSAssert(verticalSelectionColor != nil, @"JBLineChartView // delegate function - (UIColor *)lineChartView:(JBLineChartView *)lineChartView verticalSelectionColorForLineAtLineIndex:(NSUInteger)lineIndex must return a non-nil UIColor"); - self.verticalSelectionView.bgColor = verticalSelectionColor; - } - - - CGFloat xOffset = fmin(self.bounds.size.width - self.verticalSelectionView.frame.size.width, fmax(0, touchPoint.x - (ceil(self.verticalSelectionView.frame.size.width * 0.5)))); - CGFloat yOffset = self.headerView.frame.size.height + self.headerPadding; - - if ([self.dataSource respondsToSelector:@selector(shouldExtendSelectionViewIntoHeaderPaddingForChartView:)]) - { - if ([self.dataSource shouldExtendSelectionViewIntoHeaderPaddingForChartView:self]) - { - yOffset = self.headerView.frame.size.height; - } - } - - self.verticalSelectionView.frame = CGRectMake(xOffset, yOffset, self.verticalSelectionView.frame.size.width, self.verticalSelectionView.frame.size.height); - [self setVerticalSelectionViewVisible:YES animated:YES]; -} - -- (void)touchesEndedOrCancelledWithTouches:(NSSet *)touches -{ - if (self.state == JBChartViewStateCollapsed || [self.chartData count] <= 0) - { - return; - } - - [self setVerticalSelectionViewVisible:NO animated:YES]; - - if ([self.delegate respondsToSelector:@selector(didDeselectLineInLineChartView:)]) - { - [self.delegate didDeselectLineInLineChartView:self]; - } - - if (self.showsLineSelection) - { - [self.linesView setSelectedLineIndex:kJBLineChartLinesViewUnselectedLineIndex animated:YES]; - [self.dotsView setSelectedLineIndex:kJBLineChartDotsViewUnselectedLineIndex animated:YES]; - } -} - -#pragma mark - Gestures - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - UITouch *touch = [touches anyObject]; - CGPoint touchPoint = [self clampPoint:[touch locationInView:self.linesView] toBounds:self.linesView.bounds padding:[self padding]]; - if (self.showsLineSelection) - { - [self.linesView setSelectedLineIndex:[self lineIndexForPoint:touchPoint] animated:YES]; - [self.dotsView setSelectedLineIndex:[self lineIndexForPoint:touchPoint] animated:YES]; - } - [self touchesBeganOrMovedWithTouches:touches]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesBeganOrMovedWithTouches:touches]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesEndedOrCancelledWithTouches:touches]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - [self touchesEndedOrCancelledWithTouches:touches]; -} - -@end - -@implementation JBLineLayer - -#pragma mark - Alloc/Init - -+ (void)initialize -{ - if (self == [JBLineLayer class]) - { - kJBLineChartLineViewDefaultDashPattern = @[@(3), @(2)]; - } -} - -- (id)init -{ - self = [super init]; - if (self) - { - self.zPosition = 0.1f; - self.fillColor = [UIColor clearColor].CGColor; - } - return self; -} - -#pragma mark - Setters - -- (void)setLineStyle:(JBLineChartViewLineStyle)lineStyle -{ - _lineStyle = lineStyle; - - if (_lineStyle == JBLineChartViewLineStyleDashed) - { - self.lineDashPhase = kJBLineChartLinesViewDefaultLinePhase; - self.lineDashPattern = kJBLineChartLineViewDefaultDashPattern; - } - else if (_lineStyle == JBLineChartViewLineStyleSolid) - { - self.lineDashPhase = 0.0; - self.lineDashPattern = nil; - } -} - -@end - -@implementation JBFillLayer - -#pragma mark - Alloc/Init - -- (id)init -{ - self = [super init]; - if (self) - { - self.zPosition = 0.0f; - self.strokeColor = [UIColor clearColor].CGColor; - self.lineWidth = 0.0f; - } - return self; -} - -@end - -@implementation JBLineChartPoint - -#pragma mark - Alloc/Init - -- (id)init -{ - self = [super init]; - if (self) - { - _position = CGPointZero; - } - return self; -} - -#pragma mark - Compare - -- (NSComparisonResult)compare:(JBLineChartPoint *)otherObject -{ - return self.position.x > otherObject.position.x; -} - -@end - -@implementation JBLineChartLinesView - -#pragma mark - Alloc/Init - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - self.backgroundColor = [UIColor clearColor]; - } - return self; -} - -#pragma mark - Memory Management - -- (void)dealloc -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self]; -} - -#pragma mark - Drawing - -- (void)drawRect:(CGRect)rect -{ - [super drawRect:rect]; - - NSAssert([self.delegate respondsToSelector:@selector(chartDataForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (NSArray *)chartDataForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView"); - NSArray *chartData = [self.delegate chartDataForLineChartLinesView:self]; - - NSAssert([self.delegate respondsToSelector:@selector(paddingForLineChartLinesView:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)paddingForLineChartLinesView:(JBLineChartLinesView *)lineChartLinesView"); - CGFloat padding = [self.delegate paddingForLineChartLinesView:self]; - - NSUInteger lineIndex = 0; - for (NSArray *lineData in chartData) - { - if (lineData.count == 0) - { - continue; - } - - UIBezierPath *path = [UIBezierPath bezierPath]; - path.miterLimit = kJBLineChartLinesViewMiterLimit; - - JBLineChartPoint *previousLineChartPoint = nil; - CGFloat previousSlope = 0.0f; - - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:smoothLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (BOOL)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView smoothLineAtLineIndex:(NSUInteger)lineIndex"); - BOOL smoothLine = [self.delegate lineChartLinesView:self smoothLineAtLineIndex:lineIndex]; - - BOOL visiblePointFound = NO; - NSArray *sortedLineData = [lineData sortedArrayUsingSelector:@selector(compare:)]; - CGFloat firstXPosition = 0.0f; - CGFloat lastXPosition = 0.0f; - for (NSUInteger index = 0; index < [sortedLineData count]; index++) - { - JBLineChartPoint *lineChartPoint = [sortedLineData objectAtIndex:index]; - if(lineChartPoint.hidden) { - continue; - } - - if (!visiblePointFound) - { - [path moveToPoint:CGPointMake(lineChartPoint.position.x, fmin(self.bounds.size.height - padding, fmax(padding, lineChartPoint.position.y)))]; - firstXPosition = lineChartPoint.position.x; - visiblePointFound = YES; - } - else - { - JBLineChartPoint *nextLineChartPoint = nil; - if (index != ([lineData count] - 1)) - { - nextLineChartPoint = [sortedLineData objectAtIndex:(index + 1)]; - } - - CGFloat nextSlope = (nextLineChartPoint != nil) ? ((nextLineChartPoint.position.y - lineChartPoint.position.y)) / ((nextLineChartPoint.position.x - lineChartPoint.position.x)) : previousSlope; - CGFloat currentSlope = ((lineChartPoint.position.y - previousLineChartPoint.position.y)) / (lineChartPoint.position.x-previousLineChartPoint.position.x); - - BOOL deltaFromNextSlope = ((currentSlope >= (nextSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (nextSlope - kJBLineChartLinesViewSmoothThresholdSlope))); - BOOL deltaFromPreviousSlope = ((currentSlope >= (previousSlope + kJBLineChartLinesViewSmoothThresholdSlope)) || (currentSlope <= (previousSlope - kJBLineChartLinesViewSmoothThresholdSlope))); - BOOL deltaFromPreviousY = (lineChartPoint.position.y >= previousLineChartPoint.position.y + kJBLineChartLinesViewSmoothThresholdVertical) || (lineChartPoint.position.y <= previousLineChartPoint.position.y - kJBLineChartLinesViewSmoothThresholdVertical); - - if (smoothLine && deltaFromNextSlope && deltaFromPreviousSlope && deltaFromPreviousY) - { - CGFloat deltaX = lineChartPoint.position.x - previousLineChartPoint.position.x; - CGFloat controlPointX = previousLineChartPoint.position.x + (deltaX / 2); - - CGPoint controlPoint1 = CGPointMake(controlPointX, previousLineChartPoint.position.y); - CGPoint controlPoint2 = CGPointMake(controlPointX, lineChartPoint.position.y); - - [path addCurveToPoint:CGPointMake(lineChartPoint.position.x, fmin(self.bounds.size.height - padding, fmax(padding, lineChartPoint.position.y))) controlPoint1:controlPoint1 controlPoint2:controlPoint2]; - } - else - { - [path addLineToPoint:CGPointMake(lineChartPoint.position.x, fmin(self.bounds.size.height - padding, fmax(padding, lineChartPoint.position.y)))]; - } - - lastXPosition = lineChartPoint.position.x; - previousSlope = currentSlope; - } - previousLineChartPoint = lineChartPoint; - } - - JBLineLayer *shapeLayer = [self lineLayerForLineIndex:lineIndex]; - if (shapeLayer == nil) - { - shapeLayer = [JBLineLayer layer]; - } - - JBFillLayer *shapeFillLayer = [self fillLayerForLineIndex:lineIndex]; - if (shapeFillLayer == nil) - { - shapeFillLayer = [JBFillLayer layer]; - } - - shapeLayer.tag = lineIndex; - shapeFillLayer.tag = lineIndex; - - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:lineStyleForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (JBLineChartViewLineStyle)lineChartLineView:(JBLineChartLinesView *)lineChartLinesView lineStyleForLineAtLineIndex:(NSUInteger)lineIndex"); - shapeLayer.lineStyle = [self.delegate lineChartLinesView:self lineStyleForLineAtLineIndex:lineIndex]; - - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex"); - shapeLayer.strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:lineIndex].CGColor; - - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex"); - shapeFillLayer.fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:lineIndex].CGColor; - - if (smoothLine == YES) - { - shapeLayer.lineCap = kCALineCapRound; - shapeLayer.lineJoin = kCALineJoinRound; - shapeFillLayer.lineCap = kCALineCapRound; - shapeFillLayer.lineJoin = kCALineJoinRound; - } - else - { - shapeLayer.lineCap = kCALineCapButt; - shapeLayer.lineJoin = kCALineJoinMiter; - shapeFillLayer.lineCap = kCALineCapButt; - shapeFillLayer.lineJoin = kCALineJoinMiter; - } - - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:widthForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (CGFloat)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView widthForLineAtLineIndex:(NSUInteger)lineIndex"); - shapeLayer.lineWidth = [self.delegate lineChartLinesView:self widthForLineAtLineIndex:lineIndex]; - shapeLayer.path = path.CGPath; - shapeLayer.frame = self.bounds; - - // Continue the path for the fill layer (close and fill) - UIBezierPath *fillPath = [path copy]; - - if(visiblePointFound) - { - [fillPath addLineToPoint:CGPointMake(lastXPosition, self.bounds.size.height - padding)]; - [fillPath addLineToPoint:CGPointMake(firstXPosition, self.bounds.size.height - padding)]; - } - - shapeFillLayer.path = fillPath.CGPath; - shapeFillLayer.frame = self.bounds; - - [self.layer addSublayer:shapeFillLayer]; - [self.layer addSublayer:shapeLayer]; - - lineIndex++; - } - - self.animated = NO; -} - -#pragma mark - Data - -- (void)reloadData -{ - // Drawing is all done with CG (no subviews here) - [self setNeedsDisplay]; -} - -#pragma mark - Setters - -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated -{ - _selectedLineIndex = selectedLineIndex; - - __weak JBLineChartLinesView* weakSelf = self; - - dispatch_block_t adjustLines = ^{ - for (CALayer *layer in [weakSelf.layer sublayers]) - { - if ([layer isKindOfClass:[JBLineLayer class]]) - { - if (((NSInteger)((JBLineLayer *)layer).tag) == weakSelf.selectedLineIndex) - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectedColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedColorForLineAtLineIndex:(NSUInteger)lineIndex"); - ((JBLineLayer *)layer).strokeColor = [self.delegate lineChartLinesView:self selectedColorForLineAtLineIndex:((JBLineLayer *)layer).tag].CGColor; - ((JBLineLayer *)layer).opacity = 1.0f; - } - else - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:colorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView colorForLineAtLineIndex:(NSUInteger)lineIndex"); - ((JBLineLayer *)layer).strokeColor = [self.delegate lineChartLinesView:self colorForLineAtLineIndex:((JBLineLayer *)layer).tag].CGColor; - ((JBLineLayer *)layer).opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : kJBLineChartLinesViewDefaultDimmedOpacity; - } - } - else if ([layer isKindOfClass:[JBFillLayer class]]) - { - if (((NSInteger)((JBFillLayer *)layer).tag) == weakSelf.selectedLineIndex) - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:selectedFillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView selectedFillColorForLineAtLineIndex:(NSUInteger)lineIndex"); - ((JBFillLayer *)layer).fillColor = [self.delegate lineChartLinesView:self selectedFillColorForLineAtLineIndex:((JBLineLayer *)layer).tag].CGColor; - ((JBFillLayer *)layer).opacity = 1.0f; - } - else - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartLinesView:fillColorForLineAtLineIndex:)], @"JBLineChartLinesView // delegate must implement - (UIColor *)lineChartLinesView:(JBLineChartLinesView *)lineChartLinesView fillColorForLineAtLineIndex:(NSUInteger)lineIndex"); - ((JBFillLayer *)layer).fillColor = [self.delegate lineChartLinesView:self fillColorForLineAtLineIndex:((JBLineLayer *)layer).tag].CGColor; - ((JBFillLayer *)layer).opacity = (weakSelf.selectedLineIndex == kJBLineChartLinesViewUnselectedLineIndex) ? 1.0f : kJBLineChartLinesViewDefaultDimmedOpacity; - } - } - } - }; - - if (animated) - { - [UIView animateWithDuration:kJBChartViewDefaultAnimationDuration animations:^{ - adjustLines(); - }]; - } - else - { - adjustLines(); - } -} - -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex -{ - [self setSelectedLineIndex:selectedLineIndex animated:NO]; -} - -#pragma mark - Callback Helpers - -- (void)fireCallback:(void (^)())callback -{ - dispatch_block_t callbackCopy = [callback copy]; - - if (callbackCopy != nil) - { - callbackCopy(); - } -} - -#pragma mark - View Helpers - -- (JBLineLayer *)lineLayerForLineIndex:(NSUInteger)lineIndex -{ - for (CALayer *layer in [self.layer sublayers]) - { - if ([layer isKindOfClass:[JBLineLayer class]]) - { - if (((JBLineLayer *)layer).tag == lineIndex) - { - return (JBLineLayer *)layer; - } - } - } - return nil; -} - -- (JBFillLayer *)fillLayerForLineIndex:(NSUInteger)lineIndex -{ - for (CALayer *layer in [self.layer sublayers]) - { - if ([layer isKindOfClass:[JBFillLayer class]]) - { - if (((JBFillLayer *)layer).tag == lineIndex) - { - return (JBFillLayer *)layer; - } - } - } - return nil; -} - -@end - -@implementation JBLineChartDotsView - -#pragma mark - Alloc/Init - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - self.backgroundColor = [UIColor clearColor]; - } - return self; -} - -#pragma mark - Data - -- (void)reloadData -{ - for (NSArray *dotViews in [self.dotViewsDict allValues]) - { - for (JBLineChartDotView *dotView in dotViews) - { - [dotView removeFromSuperview]; - } - } - - NSAssert([self.delegate respondsToSelector:@selector(chartDataForLineChartDotsView:)], @"JBLineChartDotsView // delegate must implement - (NSArray *)chartDataForLineChartDotsView:(JBLineChartDotsView *)lineChartDotsView"); - NSArray *chartData = [self.delegate chartDataForLineChartDotsView:self]; - - NSAssert([self.delegate respondsToSelector:@selector(paddingForLineChartDotsView:)], @"JBLineChartDotsView // delegate must implement - (CGFloat)paddingForLineChartDotsView:(JBLineChartDotsView *)lineChartDotsView"); - CGFloat padding = [self.delegate paddingForLineChartDotsView:self]; - - NSUInteger lineIndex = 0; - NSMutableDictionary *mutableDotViewsDict = [NSMutableDictionary dictionary]; - for (NSArray *lineData in chartData) - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:showsDotsForLineAtLineIndex:)], @"JBLineChartDotsView // delegate must implement - (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView showsDotsForLineAtLineIndex:(NSUInteger)lineIndex"); - - if ([self.delegate lineChartDotsView:self showsDotsForLineAtLineIndex:lineIndex]) // line at index contains dots - { - NSMutableArray *mutableDotViews = [NSMutableArray array]; - NSArray *sortedLineData = [lineData sortedArrayUsingSelector:@selector(compare:)]; - for (NSUInteger horizontalIndex = 0; horizontalIndex < [sortedLineData count]; horizontalIndex++) - { - JBLineChartPoint *lineChartPoint = [sortedLineData objectAtIndex:horizontalIndex]; - if(lineChartPoint.hidden) - { - continue; - } - - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:dotViewAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (UIView *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotViewAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - UIView *currentDotView = [self.delegate lineChartDotsView:self dotViewAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - - // System dot - if (currentDotView == nil) - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:dotRadiusForLineAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (CGFloat)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView dotRadiusForLineAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - CGFloat dotRadius = [self.delegate lineChartDotsView:self dotRadiusForLineAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - - currentDotView = [[JBLineChartDotView alloc] initWithRadius:dotRadius]; - - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:colorForDotAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView colorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - currentDotView.backgroundColor = [self.delegate lineChartDotsView:self colorForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - - currentDotView.center = CGPointMake(lineChartPoint.position.x, fmin(self.bounds.size.height - padding, fmax(padding, lineChartPoint.position.y))); - [mutableDotViews addObject:currentDotView]; - [self addSubview:currentDotView]; - - } - [mutableDotViewsDict setObject:[NSArray arrayWithArray:mutableDotViews] forKey:[NSNumber numberWithInteger:lineIndex]]; - } - lineIndex++; - } - self.dotViewsDict = [NSDictionary dictionaryWithDictionary:mutableDotViewsDict]; -} - -#pragma mark - Setters - -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex animated:(BOOL)animated -{ - _selectedLineIndex = selectedLineIndex; - - __weak JBLineChartDotsView* weakSelf = self; - - dispatch_block_t adjustDots = ^{ - [weakSelf.dotViewsDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - NSUInteger horizontalIndex = 0; - for (UIView *dotView in (NSArray *)obj) - { - if ([key isKindOfClass:[NSNumber class]]) - { - NSInteger lineIndex = [((NSNumber *)key) intValue]; - - // Internal dot - if ([dotView isKindOfClass:[JBLineChartDotView class]]) - { - if (weakSelf.selectedLineIndex == lineIndex) - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:selectedColorForDotAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView selectedColorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - dotView.backgroundColor = [self.delegate lineChartDotsView:self selectedColorForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - } - else - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:colorForDotAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (UIColor *)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView colorForDotAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - dotView.backgroundColor = [self.delegate lineChartDotsView:self colorForDotAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - dotView.alpha = (weakSelf.selectedLineIndex == kJBLineChartDotsViewUnselectedLineIndex) ? 1.0f : 0.0f; // hide dots on off-selection - } - } - // Custom dot - else - { - NSAssert([self.delegate respondsToSelector:@selector(lineChartDotsView:shouldHideDotViewOnSelectionAtHorizontalIndex:atLineIndex:)], @"JBLineChartDotsView // delegate must implement - (BOOL)lineChartDotsView:(JBLineChartDotsView *)lineChartDotsView shouldHideDotViewOnSelectionAtHorizontalIndex:(NSUInteger)horizontalIndex atLineIndex:(NSUInteger)lineIndex"); - BOOL hideDotView = [self.delegate lineChartDotsView:self shouldHideDotViewOnSelectionAtHorizontalIndex:horizontalIndex atLineIndex:lineIndex]; - if (weakSelf.selectedLineIndex == lineIndex) - { - dotView.alpha = hideDotView ? 0.0f : 1.0f; - } - else - { - dotView.alpha = 1.0; - } - } - } - horizontalIndex++; - } - }]; - }; - - if (animated) - { - [UIView animateWithDuration:kJBChartViewDefaultAnimationDuration animations:^{ - adjustDots(); - }]; - } - else - { - adjustDots(); - } -} - -- (void)setSelectedLineIndex:(NSInteger)selectedLineIndex -{ - [self setSelectedLineIndex:selectedLineIndex animated:NO]; -} - -@end - -@implementation JBLineChartDotView - -#pragma mark - Alloc/Init - -- (id)initWithRadius:(CGFloat)radius -{ - self = [super initWithFrame:CGRectMake(0, 0, (radius * 2.0f), (radius * 2.0f))]; - if (self) - { - self.clipsToBounds = YES; - self.layer.cornerRadius = ((radius * 2.0f) * 0.5f); - } - return self; -} - -@end diff --git a/ios/PhyWeb/third-party/JBChartView/LICENSE b/ios/PhyWeb/third-party/JBChartView/LICENSE deleted file mode 100644 index a43bcf0e..00000000 --- a/ios/PhyWeb/third-party/JBChartView/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2013 Jawbone Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/ios/PhyWeb/third-party/JBChartView/README b/ios/PhyWeb/third-party/JBChartView/README deleted file mode 100644 index 19b74d8f..00000000 --- a/ios/PhyWeb/third-party/JBChartView/README +++ /dev/null @@ -1 +0,0 @@ -Original repository: https://github.com/Jawbone/JBChartView diff --git a/ios/PhyWeb/third-party/MBProgressHUD b/ios/PhyWeb/third-party/MBProgressHUD deleted file mode 160000 index 28c44be4..00000000 --- a/ios/PhyWeb/third-party/MBProgressHUD +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 28c44be41399e474ed959269e2364be0047ad8eb diff --git a/ios/PhyWeb/third-party/SDWebImage b/ios/PhyWeb/third-party/SDWebImage deleted file mode 160000 index 14842f65..00000000 --- a/ios/PhyWeb/third-party/SDWebImage +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14842f65f79226f239e6236ecc622b0d496fc5b8 diff --git a/ios/PhyWeb/third-party/SVPullToRefresh b/ios/PhyWeb/third-party/SVPullToRefresh deleted file mode 160000 index b6576878..00000000 --- a/ios/PhyWeb/third-party/SVPullToRefresh +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6576878d5e2a207e0122dae3663979ebf0e1d1f diff --git a/ios/README.md b/ios/README.md deleted file mode 100644 index e8211ab0..00000000 --- a/ios/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# iOS client - -This iOS client can be downloaded from the [Apple App Store](https://itunes.apple.com/us/app/physical-web/id927653608?mt=8). A walkthrough of the app [is here](http://github.com/google/physical-web/blob/master/documentation/android_client_walkthrough.md) (Android version) 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/ios/scripts/make-dev.sh b/ios/scripts/make-dev.sh deleted file mode 100755 index 46fac9e6..00000000 --- a/ios/scripts/make-dev.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -folder="`pwd`" -version=`defaults read "$folder/Info.plist" CFBundleShortVersionString` -version=`echo $version|sed -e 's/\(.*\)-.*/\1/'` -defaults write "$folder/Info.plist" CFBundleShortVersionString "${version}-dev" diff --git a/ios/scripts/update-build.sh b/ios/scripts/update-build.sh deleted file mode 100755 index 4ed22c21..00000000 --- a/ios/scripts/update-build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -folder="`pwd`" -build_version=`defaults read "$folder/Info.plist" CFBundleVersion` -build_version=$(($build_version+1)) -defaults write "$folder/Info.plist" CFBundleVersion $build_version -version=`defaults read "$folder/Info.plist" CFBundleShortVersionString` -version=`echo $version|sed -e 's/\(.*\)-.*/\1/'` -defaults write "$folder/Info.plist" CFBundleShortVersionString $version diff --git a/ios/today/Info.plist b/ios/today/Info.plist deleted file mode 100644 index 918be289..00000000 --- a/ios/today/Info.plist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - Physical Web Beacons - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.physical-web.iosapp.today - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - XPC! - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 16 - NSExtension - - NSExtensionPrincipalClass - TodayViewController - NSExtensionPointIdentifier - com.apple.widget-extension - - - diff --git a/ios/today/TodayViewController.h b/ios/today/TodayViewController.h deleted file mode 100644 index fd7d052d..00000000 --- a/ios/today/TodayViewController.h +++ /dev/null @@ -1,21 +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. - */ - -#import - -@interface TodayViewController : UIViewController - -@end diff --git a/ios/today/TodayViewController.m b/ios/today/TodayViewController.m deleted file mode 100644 index c0040188..00000000 --- a/ios/today/TodayViewController.m +++ /dev/null @@ -1,321 +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. - */ - -#import "TodayViewController.h" -#import -#import -#import "PWBeaconManager.h" -#import "PWBeacon.h" -#import "PWBeaconCell.h" - -#define ROW_HEIGHT 70 - -#define DEBUG_TODAY 0 - -@interface TodayViewController () - -@end - -@implementation TodayViewController { - NSMutableArray *_beacons; - UITableView *_tableView; - void (^_completionHandler)(NCUpdateResult); - BOOL _scheduledUpdated; - BOOL _firstUpdate; - CBCentralManager *_centralManager; - UILabel *_label; - NSDate *_updateDate; - BOOL _requestedOnce; -} - -- (id)initWithNibName:(NSString *)nibNameOrNil - bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - [[PWBeaconManager sharedManager] - registerChangeBlock:^{ [self _updateViewAfterDelay]; }]; - _centralManager = - [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; - return self; -} - -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - [self _updateBluetoothState]; -} - -- (void)_updateBluetoothState { - NSString *stateText = @"(none)"; - switch ([_centralManager state]) { - case CBCentralManagerStateUnknown: - stateText = @"Unknown"; - break; - case CBCentralManagerStateResetting: - stateText = @"Resetting"; - break; - case CBCentralManagerStateUnsupported: - stateText = @"Unsupported"; - break; - case CBCentralManagerStateUnauthorized: - stateText = @"Unauthorized"; - break; - case CBCentralManagerStatePoweredOff: - stateText = @"Powered off"; - break; - case CBCentralManagerStatePoweredOn: - stateText = @"Powered on"; - break; - } - - NSString *text = - [NSString stringWithFormat:@"%@, %@", stateText, _updateDate]; - [_label setText:text]; - [_label sizeToFit]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGRect bounds = [[self view] bounds]; - _tableView = - [[UITableView alloc] initWithFrame:bounds style:UITableViewStylePlain]; - [_tableView setDataSource:self]; - [_tableView setDelegate:self]; - [_tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | - UIViewAutoresizingFlexibleWidth]; - [_tableView setRowHeight:ROW_HEIGHT]; - [[self view] addSubview:_tableView]; - [_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; - _label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)]; - [_label setBackgroundColor:[UIColor redColor]]; -#if !DEBUG_TODAY - [_label setHidden:YES]; -#endif - [[self view] addSubview:_label]; - - [self _updateBluetoothState]; -} - -- (void)_updateViewAfterDelay { - if (_scheduledUpdated) { - return; - } - - _updateDate = [NSDate date]; - [self _updateBluetoothState]; - - _scheduledUpdated = YES; - // The first update will be scheduled after 1 sec, the subsequent updates will - // be scheduled after 5 sec. If the list was empty, an update will also be - // scheduled after 1 sec. - NSTimeInterval delay = 1; - if (_firstUpdate || [_beacons count] == 0) { - delay = 1; - } - [self performSelector:@selector(_updateBeaconsNow) - withObject:nil - afterDelay:delay]; - _firstUpdate = NO; -} - -- (void)_updateBeaconsNow { - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(_updateBeaconsNow) - object:nil]; - _scheduledUpdated = NO; - _beacons = [[[PWBeaconManager sharedManager] beacons] mutableCopy]; - [self _sort]; - [self _reloadData]; - - [self _saveBeacons]; -} - -// Sort results by RSSI value. -- (void)_sort { - [_beacons sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - PWBeacon *beacon1 = obj1; - PWBeacon *beacon2 = obj2; - NSInteger regionDifference = (NSInteger)[[beacon1 uriBeacon] region] - - (NSInteger)[[beacon2 uriBeacon] region]; - if (regionDifference > 0) { - return NSOrderedDescending; - } else if (regionDifference < 0) { - return NSOrderedAscending; - } else { - return [[beacon1 title] caseInsensitiveCompare:[beacon2 title]]; - } - }]; -} - -- (void)_reloadData { - [_tableView reloadData]; - CGSize size = [_tableView contentSize]; - CGRect frame = [_tableView frame]; - frame.size.height = size.height; - frame.origin = CGPointZero; - [_tableView setFrame:frame]; - self.preferredContentSize = size; -} - -- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets: - (UIEdgeInsets)defaultMarginInsets { - return UIEdgeInsetsMake(0, 0, 30, 0); -} - -- (void)widgetPerformUpdateWithCompletionHandler: - (void (^)(NCUpdateResult))completionHandler { - [self performSelector:@selector(_loadingDone) withObject:nil afterDelay:2]; - if ([[PWBeaconManager sharedManager] isStarted]) { - [[PWBeaconManager sharedManager] stop]; - } - - _updateDate = [NSDate date]; - [self _updateBluetoothState]; - - _completionHandler = completionHandler; - - _firstUpdate = YES; - _requestedOnce = NO; - [[PWBeaconManager sharedManager] resetBeacons]; - [[PWBeaconManager sharedManager] start]; - [self _updateViewAfterDelay]; - - [self _loadBeacons]; - [self _reloadData]; - - _completionHandler(NCUpdateResultNewData); -} - -- (void)_loadingDone { - _requestedOnce = YES; - [self _reloadData]; -} - -- (NSInteger)tableView:(UITableView *)tableView - numberOfRowsInSection:(NSInteger)section { - if ([_beacons count] == 0) { - return 1; - } else if ([_beacons count] > 3) { - return 4; - } else { - return [_beacons count]; - } -} - -- (UITableViewCell *)tableView:(UITableView *)tableView - cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([_beacons count] == 0) { - UITableViewCell *cell = - [tableView dequeueReusableCellWithIdentifier:@"no-beacons"]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault - reuseIdentifier:@"no-beacons"]; - UILabel *label = [[UILabel alloc] - initWithFrame:CGRectMake(50, 0, [_tableView frame].size.width - 50, - 30)]; - [label setTag:150]; - [cell addSubview:label]; - [label setTextColor:[UIColor colorWithWhite:0.5 alpha:1.0]]; - [label setFont:[UIFont systemFontOfSize:14]]; - UIView *selectedBackgroundView = [[UIView alloc] - initWithFrame:CGRectMake(0, 0, [_tableView frame].size.width, 30)]; - [selectedBackgroundView - setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:0.07]]; - [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; - [cell setSelectedBackgroundView:selectedBackgroundView]; - } - UILabel *label = (UILabel *) [cell viewWithTag:150]; - NSString *noBeaconText = nil; - if (_requestedOnce) { - noBeaconText = [NSString stringWithFormat:@"No beacon nearby"]; - } else { - noBeaconText = [NSString stringWithFormat:@"Scanning..."]; - } - [label setText:noBeaconText]; - return cell; - } else if ([indexPath row] == 3) { - UITableViewCell *cell = - [tableView dequeueReusableCellWithIdentifier:@"more"]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault - reuseIdentifier:@"more"]; - UILabel *label = [[UILabel alloc] - initWithFrame:CGRectMake(50, 0, [_tableView frame].size.width - 50, - 30)]; - [cell addSubview:label]; - [label setTextColor:[UIColor colorWithWhite:0.5 alpha:1.0]]; - NSString *moreText = - [NSString stringWithFormat:@"%i more beacons nearby...", - (int)[_beacons count] - 3]; - [label setText:moreText]; - [label setFont:[UIFont systemFontOfSize:14]]; - UIView *selectedBackgroundView = [[UIView alloc] - initWithFrame:CGRectMake(0, 0, [_tableView frame].size.width, 30)]; - [selectedBackgroundView - setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:0.07]]; - [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; - [cell setSelectedBackgroundView:selectedBackgroundView]; - } - return cell; - } else { - PWBeaconCell *cell = - [tableView dequeueReusableCellWithIdentifier:@"device"]; - if (cell == nil) { - cell = [[PWBeaconCell alloc] initWithStyle:UITableViewCellStyleSubtitle - reuseIdentifier:@"device"]; - } - PWBeacon *beacon = [_beacons objectAtIndex:[indexPath row]]; - [cell setBeacon:beacon]; - return cell; - } -} - -- (void)tableView:(UITableView *)tableView - didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [_tableView deselectRowAtIndexPath:indexPath animated:YES]; - if ([_beacons count] == 0) { - return; - } - if ([indexPath row] == 3) { - [[self extensionContext] openURL:[NSURL URLWithString:@"x-physweb:"] - completionHandler:nil]; - } else { - PWBeacon *beacon = [_beacons objectAtIndex:[indexPath row]]; - [[self extensionContext] openURL:[beacon URL] completionHandler:nil]; - } -} - -- (CGFloat)tableView:(UITableView *)tableView - heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if ([_beacons count] == 0) { - return 30; - } else if ([indexPath row] == 3) { - return 30; - } else { - return ROW_HEIGHT; - } -} - -- (void)_saveBeacons { - [[PWBeaconManager sharedManager] serializeBeacons:_beacons]; -} - -- (void)_loadBeacons { - _beacons = - [[[PWBeaconManager sharedManager] unserializedBeacons] mutableCopy]; -} - -@end diff --git a/ios/today/today.entitlements b/ios/today/today.entitlements deleted file mode 100644 index bf1edc86..00000000 --- a/ios/today/today.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.physical-web.iosapp - - - diff --git a/java/libs/.gitignore b/java/libs/.gitignore deleted file mode 100644 index 6c6edba0..00000000 --- a/java/libs/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.iml -.gradle -.idea -build diff --git a/java/libs/README.md b/java/libs/README.md deleted file mode 100644 index 6a768e56..00000000 --- a/java/libs/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Physical Web Collection library for java - -This java library contains data structures and convenience utilities for -storing metadata related to devices that broadcast URLs. This library is -intended to help bootstrap new Physical Web clients written in java. diff --git a/java/libs/build.gradle b/java/libs/build.gradle deleted file mode 100644 index fff44835..00000000 --- a/java/libs/build.gradle +++ /dev/null @@ -1,98 +0,0 @@ -plugins { - id 'checkstyle' - id 'findbugs' - id 'java' - id 'pmd' -} - -repositories { - jcenter() -} - -sourceSets { - integrationTest { - java { - compileClasspath += main.output + test.output - runtimeClasspath += main.output + test.output - srcDir file('src/integrationTest/java') - } - } -} - -configurations { - integrationTestCompile.extendsFrom testCompile - integrationTestRuntime.extendsFrom testRuntime -} - -gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } -} - -dependencies { - compile 'org.json:json:20140107' - testCompile 'junit:junit:4.11' - testCompile 'org.skyscreamer:jsonassert:1.2.3' -} - -task integrationTest(type: Test) { - testClassesDir = sourceSets.integrationTest.output.classesDir - classpath = sourceSets.integrationTest.runtimeClasspath - outputs.upToDateWhen { false } -} - -findbugsMain { - ignoreFailures = false - effort = "max" - reportLevel = "low" - reports { - xml.enabled = false - html.enabled = true - } -} - -findbugsTest { - ignoreFailures = false - effort = "max" - reportLevel = "low" - reports { - xml.enabled = false - html.enabled = true - } -} - -findbugs { - reportsDir = new File(buildDir, "reports/findbugs") - excludeFilter = new File(projectDir, "config/findbugs/exclude-filter.xml") -} - -checkstyle { - configProperties.checkstyleSuppressionsPath = - new File(projectDir, "config/checkstyle/suppressions.xml") -} - -pmdMain { - reports { - xml.enabled = false - html.enabled = true - } -} - -pmd { - ignoreFailures = false - ruleSetFiles = files(new File(projectDir, "config/pmd/pmd-ruleset.xml")) - ruleSets = [] - reportsDir = new File(buildDir, "reports/findbugs") -} - -javadoc { - source = sourceSets.main.java - classpath = sourceSets.main.runtimeClasspath -} - -tasks.withType(Test) { - reports.html.destination = file("${reporting.baseDir}/${name}") -} - -integrationTest.dependsOn assemble diff --git a/java/libs/config/checkstyle/checkstyle.xml b/java/libs/config/checkstyle/checkstyle.xml deleted file mode 100644 index f2182575..00000000 --- a/java/libs/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/libs/config/checkstyle/suppressions.xml b/java/libs/config/checkstyle/suppressions.xml deleted file mode 100644 index 0a255b21..00000000 --- a/java/libs/config/checkstyle/suppressions.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/java/libs/config/findbugs/exclude-filter.xml b/java/libs/config/findbugs/exclude-filter.xml deleted file mode 100644 index 5178d6ba..00000000 --- a/java/libs/config/findbugs/exclude-filter.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/java/libs/config/pmd/pmd-ruleset.xml b/java/libs/config/pmd/pmd-ruleset.xml deleted file mode 100644 index 962c4268..00000000 --- a/java/libs/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/java/libs/gradle/wrapper/gradle-wrapper.jar b/java/libs/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index c97a8bdb..00000000 Binary files a/java/libs/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/java/libs/gradle/wrapper/gradle-wrapper.properties b/java/libs/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index b55dd4d4..00000000 --- a/java/libs/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Mon Oct 05 16:09:44 PDT 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip diff --git a/java/libs/gradlew b/java/libs/gradlew deleted file mode 100755 index 91a7e269..00000000 --- a/java/libs/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/java/libs/gradlew.bat b/java/libs/gradlew.bat deleted file mode 100644 index aec99730..00000000 --- a/java/libs/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/java/libs/settings.gradle b/java/libs/settings.gradle deleted file mode 100644 index f066a64c..00000000 --- a/java/libs/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'libs' diff --git a/java/libs/src/integrationTest/java/org/physical_web/collection/IntegrationTest.java b/java/libs/src/integrationTest/java/org/physical_web/collection/IntegrationTest.java deleted file mode 100644 index 8d183aa1..00000000 --- a/java/libs/src/integrationTest/java/org/physical_web/collection/IntegrationTest.java +++ /dev/null @@ -1,128 +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.collection; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; - -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * PhysicalWebCollection integration class. - */ -public class IntegrationTest { - private PhysicalWebCollection physicalWebCollection; - - private class FetchPwsResultsTask { - private int mNumExpected; - private int mNumFound; - private int mNumMissing; - private Semaphore mMutex; - private Exception mErr; - - public FetchPwsResultsTask(int numExpected) { - mNumExpected = numExpected; - mNumFound = 0; - mNumMissing = 0; - mMutex = new Semaphore(1); - mErr = null; - } - - private void testDone() { - if (mNumFound + mNumMissing == mNumExpected) { - mMutex.release(); - } - } - - private void addFound() { - mNumFound += 1; - testDone(); - } - - private void addMissing() { - mNumMissing += 1; - testDone(); - } - - private void setError(Exception e) { - mErr = e; - mMutex.release(); - } - - public boolean run() throws InterruptedException { - final int numExpected = mNumExpected; - PwsResultCallback pwsResultCallback = new PwsResultCallback() { - public void onPwsResult(PwsResult pwsResult) { - addFound(); - } - - public void onPwsResultAbsent(String url) { - addMissing(); - } - - public void onPwsResultError(Collection urls, int httpResponseCode, Exception e) { - setError(e); - } - }; - PwsResultIconCallback pwsResultIconCallback = new PwsResultIconCallback() { - public void onIcon(byte[] icon) { - addFound(); - } - - public void onError(int httpResponseCode, Exception e) { - setError(e); - } - }; - - mMutex.acquire(); - physicalWebCollection.fetchPwsResults(pwsResultCallback, pwsResultIconCallback); - - if (mMutex.tryAcquire(3, TimeUnit.SECONDS)) { - mMutex.release(); - return true; - } - return false; - } - - public Exception getException() { - return mErr; - } - } - - @Before - public void setUp() { - physicalWebCollection = new PhysicalWebCollection(); - } - - @Test - public void resolveSomeUrls() throws InterruptedException { - physicalWebCollection.addUrlDevice(new RankedDevice("id1", "https://google.com", .5)); - physicalWebCollection.addUrlDevice(new RankedDevice("id2", "https://goo.gl/mo6YnG", .2)); - FetchPwsResultsTask task = new FetchPwsResultsTask(4); - assertTrue(task.run()); - assertNull(task.getException()); - List pwPairs = physicalWebCollection.getPwPairsSortedByRank(); - assertEquals(2, pwPairs.size()); - assertEquals("https://www.google.com/", pwPairs.get(0).getPwsResult().getSiteUrl()); - assertEquals("https://github.com/google/physical-web", - pwPairs.get(1).getPwsResult().getSiteUrl()); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/BitmapRequest.java b/java/libs/src/main/java/org/physical_web/collection/BitmapRequest.java deleted file mode 100644 index 7dcbd061..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/BitmapRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016 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.collection; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; - -/** - * A class that represents an HTTP request for an image. - * The response is a byte array of bitmap data. - */ -class BitmapRequest extends Request { - /** - * Construct a bitmap HTTP request. - * @param url The url to make this HTTP request to. - * @param callback The callback run when the HTTP response is received. - * The callback can be called with a null bitmap if the image - * couldn't be decoded. - * @throws MalformedURLException on invalid url - */ - public BitmapRequest(String url, RequestCallback callback) throws MalformedURLException { - super(url, callback); - } - - /** - * The callback that gets run after the request is made. - */ - public interface RequestCallback extends Request.RequestCallback {} - - /** - * Helper method to make an HTTP request. - * @param urlConnection The HTTP connection. - */ - public void writeToUrlConnection(HttpURLConnection urlConnection) throws IOException {} - - /** - * Helper method to read an HTTP response. - * @param is The InputStream. - * @return The decoded image. - */ - protected byte[] readInputStream(InputStream is) throws IOException { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - return os.toByteArray(); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/JsonObjectRequest.java b/java/libs/src/main/java/org/physical_web/collection/JsonObjectRequest.java deleted file mode 100644 index 9533f499..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/JsonObjectRequest.java +++ /dev/null @@ -1,88 +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.collection; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; - -/** - * A class that represents an HTTP request for a JSON object. - * Both the request payload and the response are JSON objects. - */ -class JsonObjectRequest extends Request { - private JSONObject mJsonObject; - - /** - * Construct a JSON object request. - * @param url The url to make this HTTP request to. - * @param jsonObject The JSON payload. - * @param callback The callback run when the HTTP response is received. - * @throws MalformedURLException on invalid url - */ - public JsonObjectRequest(String url, JSONObject jsonObject, RequestCallback callback) - throws MalformedURLException { - super(url, callback); - mJsonObject = jsonObject; - } - - /** - * The callback that gets run after the request is made. - */ - public interface RequestCallback extends Request.RequestCallback {} - - /** - * Helper method to make an HTTP request. - * @param urlConnection The HTTP connection. - */ - public void writeToUrlConnection(HttpURLConnection urlConnection) throws IOException { - urlConnection.setDoOutput(true); - urlConnection.setRequestProperty("Content-Type", "application/json"); - urlConnection.setRequestProperty("Accept", "application/json"); - urlConnection.setRequestMethod("POST"); - OutputStream os = urlConnection.getOutputStream(); - os.write(mJsonObject.toString().getBytes("UTF-8")); - os.close(); - } - - /** - * Helper method to read an HTTP response. - * @param is The InputStream. - * @return An object representing the HTTP response. - */ - protected JSONObject readInputStream(InputStream is) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); - String line; - while ((line = in.readLine()) != null) { - stringBuilder.append(line); - } - JSONObject jsonObject; - try { - jsonObject = new JSONObject(stringBuilder.toString()); - } catch (JSONException error) { - throw new IOException(error.toString()); - } - return jsonObject; - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollection.java b/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollection.java deleted file mode 100644 index 1211dfb4..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollection.java +++ /dev/null @@ -1,406 +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.collection; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Collection of Physical Web URL devices and related metadata. - */ -public class PhysicalWebCollection { - private static final int SCHEMA_VERSION = 1; - private static final String SCHEMA_VERSION_KEY = "schema"; - private static final String DEVICES_KEY = "devices"; - private static final String METADATA_KEY = "metadata"; - private PwsClient mPwsClient; - private Map mDeviceIdToUrlDeviceMap; - private Map mBroadcastUrlToPwsResultMap; - private Map mIconUrlToIconMap; - private Set mPendingBroadcastUrls; - private Set mPendingIconUrls; - - /** - * Construct a PhysicalWebCollection. - */ - public PhysicalWebCollection() { - mPwsClient = new PwsClient(); - mDeviceIdToUrlDeviceMap = new HashMap<>(); - mBroadcastUrlToPwsResultMap = new HashMap<>(); - mIconUrlToIconMap = new HashMap<>(); - mPendingBroadcastUrls = new HashSet<>(); - mPendingIconUrls = new HashSet<>(); - } - - /** - * Add a UrlDevice to the collection. - * @param urlDevice The UrlDevice to add. - */ - public void addUrlDevice(UrlDevice urlDevice) { - mDeviceIdToUrlDeviceMap.put(urlDevice.getId(), urlDevice); - } - - /** - * Add URL metadata to the collection. - * @param pwsResult The PwsResult to add. - */ - public void addMetadata(PwsResult pwsResult) { - mBroadcastUrlToPwsResultMap.put(pwsResult.getRequestUrl(), pwsResult); - } - - /** - * Add an Icon to the collection. - * @param url The url of the icon. - * @param icon The bitmap of the icon. - */ - public void addIcon(String url, byte[] icon) { - mIconUrlToIconMap.put(url, icon); - } - - /** - * Get an Icon from the collection. - * @param url The url of the icon. - * @return The associated icon. This will be null if there is no icon. - */ - public byte[] getIcon(String url) { - return mIconUrlToIconMap.get(url); - } - - /** - * Fetches a UrlDevice by its ID. - * @param id The ID of the UrlDevice. - * @return the UrlDevice with the given ID. - */ - public UrlDevice getUrlDeviceById(String id) { - return mDeviceIdToUrlDeviceMap.get(id); - } - - /** - * Fetches cached URL metadata using the URL broadcasted by the Physical Web device. - * @param broadcastUrl The URL broadcasted by the device. - * @return Cached metadata relevant to the given URL. - */ - public PwsResult getMetadataByBroadcastUrl(String broadcastUrl) { - return mBroadcastUrlToPwsResultMap.get(broadcastUrl); - } - - /** - * Create a JSON object that represents this data structure. - * @return a JSON serialization of this data structure. - */ - public JSONObject jsonSerialize() { - JSONObject jsonObject = new JSONObject(); - - // Serialize the UrlDevices - JSONArray urlDevices = new JSONArray(); - for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) { - urlDevices.put(urlDevice.jsonSerialize()); - } - jsonObject.put(DEVICES_KEY, urlDevices); - - // Serialize the URL metadata - JSONArray metadata = new JSONArray(); - for (PwsResult pwsResult : mBroadcastUrlToPwsResultMap.values()) { - metadata.put(pwsResult.jsonSerialize()); - } - jsonObject.put(METADATA_KEY, metadata); - - jsonObject.put(SCHEMA_VERSION_KEY, SCHEMA_VERSION); - return jsonObject; - } - - /** - * Populate this data structure with UrlDevices represented by a given JSON object. - * @param jsonObject a serialized PhysicalWebCollection. - * @return The PhysicalWebCollection represented by the serialized object. - * @throws PhysicalWebCollectionException on invalid or unrecognized input - */ - public static PhysicalWebCollection jsonDeserialize(JSONObject jsonObject) - throws PhysicalWebCollectionException { - // Check the schema version - int schemaVersion = jsonObject.getInt(SCHEMA_VERSION_KEY); - if (schemaVersion > SCHEMA_VERSION) { - throw new PhysicalWebCollectionException( - "Cannot handle schema version " + schemaVersion + ". " - + "This library only knows of schema version " + SCHEMA_VERSION); - } - PhysicalWebCollection collection = new PhysicalWebCollection(); - - // Deserialize the UrlDevices - JSONArray urlDevices = jsonObject.getJSONArray(DEVICES_KEY); - for (int i = 0; i < urlDevices.length(); i++) { - JSONObject urlDeviceJson = urlDevices.getJSONObject(i); - UrlDevice urlDevice = UrlDevice.jsonDeserialize(urlDeviceJson); - collection.addUrlDevice(urlDevice); - } - - // Deserialize the URL metadata - JSONArray metadata = jsonObject.getJSONArray(METADATA_KEY); - for (int i = 0; i < metadata.length(); i++) { - JSONObject pwsResultJson = metadata.getJSONObject(i); - PwsResult pwsResult = PwsResult.jsonDeserialize(pwsResultJson); - collection.addMetadata(pwsResult); - } - - return collection; - } - - /** - * Return a list of PwPairs sorted by rank in descending order. - * These PwPairs will be deduplicated by siteUrls (favoring the PwPair with - * the highest rank). - * @return a sorted list of PwPairs. - */ - public List getPwPairsSortedByRank() { - // Get all valid PwPairs. - List allPwPairs = getPwPairs(); - - // Sort the list in descending order. - Collections.sort(allPwPairs, Collections.reverseOrder()); - - // Filter the list. - return removeDuplicateSiteUrls(allPwPairs); - } - - /** - * Return a list of PwPairs sorted by rank in descending order, including only the top-ranked - * pair from each group. - * @return a sorted list of PwPairs. - */ - public List getGroupedPwPairsSortedByRank() { - // Get all valid PwPairs. - List allPwPairs = getPwPairs(); - - // Group pairs with the same groupId, keeping only the top-ranked PwPair. - List groupedPwPairs = removeDuplicateGroupIds(allPwPairs, null); - - // Sort by descending rank. - Collections.sort(groupedPwPairs, Collections.reverseOrder()); - - // Remove duplicate site URLs. - return removeDuplicateSiteUrls(groupedPwPairs); - } - - /** - * Return a list of all pairs of valid URL devices and corresponding URL metadata. - * @return list of PwPairs. - */ - private List getPwPairs() { - List allPwPairs = new ArrayList<>(); - for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) { - PwsResult pwsResult = mBroadcastUrlToPwsResultMap.get(urlDevice.getUrl()); - if (pwsResult != null) { - allPwPairs.add(new PwPair(urlDevice, pwsResult)); - } - } - return allPwPairs; - } - - /** - * Return the top-ranked PwPair for a given group ID. - * @return a PwPair. - */ - public PwPair getTopRankedPwPairByGroupId(String groupId) { - for (PwPair pwPair : getGroupedPwPairsSortedByRank()) { - if (pwPair.getPwsResult().getGroupId().equals(groupId)) { - return pwPair; - } - } - return null; - } - - /** - * If a site URL appears multiple times in the pairs list, keep only the first example. - * @param allPwPairs input PwPairs list. - * @return filtered PwPairs list with all duplicated site URLs removed. - */ - private static List removeDuplicateSiteUrls(List allPwPairs) { - List filteredPwPairs = new ArrayList<>(); - Set siteUrls = new HashSet<>(); - for (PwPair pwPair : allPwPairs) { - String siteUrl = pwPair.getPwsResult().getSiteUrl(); - if (!siteUrls.contains(siteUrl)) { - siteUrls.add(siteUrl); - filteredPwPairs.add(pwPair); - } - } - return filteredPwPairs; - } - - /** - * Given a list of PwPairs, return a filtered list such that only one PwPair from each group - * is included. - * @param allPairs Input PwPairs list. - * @param outGroupMap Optional output map from discovered group IDs to UrlGroups, may be null. - * @return Filtered PwPairs list. - */ - private static List removeDuplicateGroupIds(List allPairs, - Map outGroupMap) { - List filteredPairs = new ArrayList<>(); - Map groupMap = outGroupMap; - if (groupMap == null) { - groupMap = new HashMap<>(); - } else { - groupMap.clear(); - } - - for (PwPair pwPair : allPairs) { - PwsResult pwsResult = pwPair.getPwsResult(); - String groupId = pwsResult.getGroupId(); - if (groupId == null || groupId.equals("")) { - // Pairs without a group are always included - filteredPairs.add(pwPair); - } else { - // Create the group if it doesn't exist - UrlGroup urlGroup = groupMap.get(groupId); - if (urlGroup == null) { - urlGroup = new UrlGroup(groupId); - groupMap.put(groupId, urlGroup); - } - urlGroup.addPair(pwPair); - } - } - - for (UrlGroup urlGroup : groupMap.values()) { - filteredPairs.add(urlGroup.getTopPair()); - } - - return filteredPairs; - } - - /** - * Set the URL for making PWS requests. - * @param pwsEndpoint The new PWS endpoint. - */ - public void setPwsEndpoint(String pwsEndpoint) { - mPwsClient.setPwsEndpoint(pwsEndpoint); - } - - private class AugmentedPwsResultIconCallback extends PwsResultIconCallback { - private String mUrl; - private PwsResultIconCallback mCallback; - - AugmentedPwsResultIconCallback(String url, PwsResultIconCallback callback) { - mUrl = url; - mCallback = callback; - } - - @Override - public void onIcon(byte[] icon) { - mPendingIconUrls.remove(mUrl); - addIcon(mUrl, icon); - mCallback.onIcon(icon); - } - - @Override - public void onError(int httpResponseCode, Exception e) { - mPendingIconUrls.remove(mUrl); - mCallback.onError(httpResponseCode, e); - } - } - - /** - * Triggers an HTTP request to be made to the PWS. - * This method fetches a results from the PWS for all broadcast URLs, - * depending on the supplied parameters. - * @param pwsResultCallback The callback to run when we get an HTTPResponse. - * If this value is null, we will not fetch the PwsResults, only icons. - * @param pwsResultIconCallback The callback to run when we get a favicon. - * If this value is null, we will not fetch the icons. - */ - public void fetchPwsResults(final PwsResultCallback pwsResultCallback, - final PwsResultIconCallback pwsResultIconCallback) { - // Get new URLs to fetch. - Set newResolveUrls = new HashSet<>(); - Set newIconUrls = new HashSet<>(); - for (UrlDevice urlDevice : mDeviceIdToUrlDeviceMap.values()) { - String url = urlDevice.getUrl(); - if (!mPendingBroadcastUrls.contains(url)) { - PwsResult pwsResult = mBroadcastUrlToPwsResultMap.get(url); - if (pwsResult == null) { - newResolveUrls.add(url); - mPendingBroadcastUrls.add(url); - } else if (pwsResult.hasIconUrl() - && !mPendingIconUrls.contains(pwsResult.getIconUrl()) - && !mIconUrlToIconMap.containsKey(pwsResult.getIconUrl())) { - newIconUrls.add(pwsResult.getIconUrl()); - mPendingIconUrls.add(pwsResult.getIconUrl()); - } - } - } - - // Make the resolve request. - final Set finalResolveUrls = newResolveUrls; - PwsResultCallback augmentedCallback = new PwsResultCallback() { - @Override - public void onPwsResult(PwsResult pwsResult) { - addMetadata(pwsResult); - if (pwsResultIconCallback != null) { - PwsResultIconCallback augmentedIconCallback = - new AugmentedPwsResultIconCallback(pwsResult.getIconUrl(), pwsResultIconCallback); - mPwsClient.downloadIcon(pwsResult.getIconUrl(), augmentedIconCallback); - } - pwsResultCallback.onPwsResult(pwsResult); - } - - @Override - public void onPwsResultAbsent(String url) { - pwsResultCallback.onPwsResultAbsent(url); - } - - @Override - public void onPwsResultError(Collection urls, int httpResponseCode, Exception e) { - pwsResultCallback.onPwsResultError(urls, httpResponseCode, e); - } - - @Override - public void onResponseReceived(long durationMillis) { - for (String url : finalResolveUrls) { - mPendingBroadcastUrls.remove(url); - } - pwsResultCallback.onResponseReceived(durationMillis); - } - }; - if (pwsResultCallback != null && newResolveUrls.size() > 0) { - mPwsClient.resolve(newResolveUrls, augmentedCallback); - } - - // Make the icon requests. - if (pwsResultIconCallback != null) { - for (final String iconUrl : newIconUrls) { - PwsResultIconCallback augmentedIconCallback = - new AugmentedPwsResultIconCallback(iconUrl, pwsResultIconCallback); - mPwsClient.downloadIcon(iconUrl, augmentedIconCallback); - } - } - } - - /** - * Cancel all current HTTP requests. - */ - public void cancelAllRequests() { - mPwsClient.cancelAllRequests(); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollectionException.java b/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollectionException.java deleted file mode 100644 index 5647fde8..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PhysicalWebCollectionException.java +++ /dev/null @@ -1,29 +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.collection; - -/** - * Exception class used when PhysicalWebCollection runs into problems. - */ -public class PhysicalWebCollectionException extends Exception { - /** - * Construct a PhysicalWebCollectionException. - * @param message The detail message string. - */ - public PhysicalWebCollectionException(String message) { - super(message); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PwPair.java b/java/libs/src/main/java/org/physical_web/collection/PwPair.java deleted file mode 100644 index ec77df6f..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PwPair.java +++ /dev/null @@ -1,115 +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.collection; - -/** - * A physical web pair represents a UrlDevice and its corresponding PwsResult. - */ -public class PwPair implements Comparable { - private final UrlDevice mUrlDevice; - private final PwsResult mPwsResult; - - /** - * Construct a PwPair. - * @param urlDevice The URL device. - * @param pwsResult The metadata returned by PWS for the URL broadcast by the device. - */ - public PwPair(UrlDevice urlDevice, PwsResult pwsResult) { - mUrlDevice = urlDevice; - mPwsResult = pwsResult; - } - - /** - * Get the rank for the UrlDevice/PwsResult pair. - * @return the rank. - */ - public double getRank() { - return mUrlDevice.getRank(mPwsResult); - } - - /** - * Get the UrlDevice represented in the pair. - * @return the UrlDevice. - */ - public UrlDevice getUrlDevice() { - return mUrlDevice; - } - - /** - * Get the PwsResult represented in the pair. - * @return the PwsResult. - */ - public PwsResult getPwsResult() { - return mPwsResult; - } - - /** - * Return a hash code for this PwPair. - * @return hash code - */ - @Override - public int hashCode() { - int hash = 1; - hash = hash * 31 + Double.valueOf(getRank()).hashCode(); - hash = hash * 31 + mUrlDevice.hashCode(); - hash = hash * 31 + mPwsResult.hashCode(); - return hash; - } - - /** - * Check if two PwPairs are equal. - * @param other the PwPair to compare to. - * @return true if the PwPairs are equal. - */ - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (other instanceof PwPair) { - PwPair otherPwPair = (PwPair) other; - return getRank() == otherPwPair.getRank() && - mUrlDevice.equals(otherPwPair.mUrlDevice) && - mPwsResult.equals(otherPwPair.mPwsResult); - } - return false; - } - - /** - * Compare two PwPairs based on rank, breaking ties by comparing UrlDevice and PwsResult. - * @param other the PwPair to compare to. - * @return the comparison value. - */ - @Override - public int compareTo(PwPair other) { - if (this == other) { - return 0; - } - - int compareValue = Double.compare(getRank(), other.getRank()); - if (compareValue != 0) { - return compareValue; - } - - compareValue = mUrlDevice.compareTo(other.mUrlDevice); - if (compareValue != 0) { - return compareValue; - } - - return mPwsResult.compareTo(other.mPwsResult); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PwsClient.java b/java/libs/src/main/java/org/physical_web/collection/PwsClient.java deleted file mode 100644 index 4338e8d2..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PwsClient.java +++ /dev/null @@ -1,207 +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.collection; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -/** - * HTTP client that makes requests to the Physical Web Service. - */ -public class PwsClient { - private static final String DEFAULT_PWS_ENDPOINT = "https://url-caster.appspot.com"; - private static final String RESOLVE_SCAN_PATH = "resolve-scan"; - private String mPwsEndpoint; - private List mThreads; - - /** - * Construct a PwsClient. - */ - public PwsClient() { - this(DEFAULT_PWS_ENDPOINT); - } - - /** - * Construct a PwsClient. - * @param pwsEndpoint The URL to send requests to. - */ - public PwsClient(String pwsEndpoint) { - setPwsEndpoint(pwsEndpoint); - mThreads = new ArrayList<>(); - } - - /** - * Set the URL for making PWS requests. - * @param pwsEndpoint The new PWS endpoint. - */ - public void setPwsEndpoint(String pwsEndpoint) { - mPwsEndpoint = pwsEndpoint; - } - - private String constructPwsUrl(String path) { - return mPwsEndpoint + "/" + path; - } - - /** - * Send an HTTP request to the PWS to resolve a set of URLs. - * @param broadcastUrls The URLs to resolve. - * @param pwsResultCallback The callback to be run when the response is received. - */ - public void resolve(final Collection broadcastUrls, - final PwsResultCallback pwsResultCallback) { - // Create the response callback. - final long startTime = new Date().getTime(); - JsonObjectRequest.RequestCallback requestCallback = new JsonObjectRequest.RequestCallback() { - private void recordResponse() { - pwsResultCallback.onResponseReceived(new Date().getTime() - startTime); - } - - public void onResponse(JSONObject result) { - recordResponse(); - - // Build the metadata from the response. - JSONArray foundMetadata; - try { - foundMetadata = result.getJSONArray("metadata"); - } catch (JSONException e) { - pwsResultCallback.onPwsResultError(broadcastUrls, 200, e); - return; - } - - // Loop through the metadata for each url. - Set foundUrls = new HashSet<>(); - for (int i = 0; i < foundMetadata.length(); i++) { - String requestUrl = null; - String responseUrl = null; - String title = null; - String description = null; - String iconUrl = null; - String groupId = null; - try { - JSONObject jsonUrlMetadata = foundMetadata.getJSONObject(i); - requestUrl = jsonUrlMetadata.getString("id"); - responseUrl = jsonUrlMetadata.getString("url"); - title = jsonUrlMetadata.getString("title"); - description = jsonUrlMetadata.getString("description"); - iconUrl = jsonUrlMetadata.optString("icon"); - groupId = jsonUrlMetadata.optString("groupId"); - } catch (JSONException e) { - continue; - } - PwsResult pwsResult = - new PwsResult(requestUrl, responseUrl, title, description, iconUrl, groupId); - pwsResultCallback.onPwsResult(pwsResult); - foundUrls.add(pwsResult.getRequestUrl()); - } - - // See which urls the PWS didn't give us a response for. - Set missed = new HashSet<>(broadcastUrls); - missed.removeAll(foundUrls); - for (String url : missed) { - pwsResultCallback.onPwsResultAbsent(url); - } - } - - public void onError(int responseCode, Exception e) { - recordResponse(); - pwsResultCallback.onPwsResultError(broadcastUrls, responseCode, e); - } - }; - - // Create the request. - String targetUrl = constructPwsUrl(RESOLVE_SCAN_PATH); - JSONObject payload = new JSONObject(); - try { - JSONArray urls = new JSONArray(); - for (String url : broadcastUrls) { - JSONObject obj = new JSONObject(); - obj.put("url", url); - urls.put(obj); - } - payload.put("objects", urls); - } catch (JSONException e) { - pwsResultCallback.onPwsResultError(broadcastUrls, 0, e); - return; - } - Request request; - try { - request = new JsonObjectRequest(targetUrl, payload, requestCallback); - } catch (MalformedURLException e) { - pwsResultCallback.onPwsResultError(broadcastUrls, 0, e); - return; - } - makeRequest(request); - } - - /** - * Given an icon url returned by the PWS, fetch that icon. - * @param url The icon URL returned by the PWS. - * @param pwsResultIconCallback The callback to run on an HTTP response. - */ - public void downloadIcon(final String url, final PwsResultIconCallback pwsResultIconCallback) { - BitmapRequest.RequestCallback requestCallback = new BitmapRequest.RequestCallback() { - public void onResponse(byte[] result) { - pwsResultIconCallback.onIcon(result); - } - - public void onError(int responseCode, Exception e) { - pwsResultIconCallback.onError(responseCode, e); - } - }; - - Request request; - try { - request = new BitmapRequest(url, requestCallback); - } catch (MalformedURLException e) { - pwsResultIconCallback.onError(0, e); - return; - } - makeRequest(request); - } - - /** - * Cancel all current HTTP requests. - */ - public void cancelAllRequests() { - for (Thread thread : mThreads) { - thread.interrupt(); - } - mThreads.clear(); - } - - private void makeRequest(Request request) { - // Remove all threads that are no longer alive. - for (Iterator iterator = mThreads.iterator(); iterator.hasNext();) { - if (!iterator.next().isAlive()) { - iterator.remove(); - } - } - - // Start the new thread and record it. - request.start(); - mThreads.add(request); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PwsResult.java b/java/libs/src/main/java/org/physical_web/collection/PwsResult.java deleted file mode 100644 index bdf9e1e0..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PwsResult.java +++ /dev/null @@ -1,272 +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.collection; - -import org.json.JSONObject; - -/** - * Metadata returned from the Physical Web Service for a single URL. - */ -public class PwsResult implements Comparable { - private static final String REQUESTURL_KEY = "requesturl"; - private static final String SITEURL_KEY = "siteurl"; - private static final String TITLE_KEY = "title"; - private static final String DESCRIPTION_KEY = "description"; - private static final String ICONURL_KEY = "iconurl"; - private static final String GROUPID_KEY = "groupid"; - private static final String EXTRA_KEY = "extra"; - private String mRequestUrl; - private String mSiteUrl; - private String mTitle; - private String mDescription; - private String mIconUrl; - private String mGroupId; - private JSONObject mExtraData; - - /** - * Construct a PwsResult. - * @param requestUrl The request URL, as broadcasted by the device - * @param siteUrl The site URL, as reported by PWS - * @param groupId The URL group ID, as reported by PWS - */ - public PwsResult( - String requestUrl, String siteUrl, String title, String description, String iconUrl, - String groupId) { - mRequestUrl = requestUrl; - mSiteUrl = siteUrl; - mIconUrl = iconUrl; - mTitle = title; - mDescription = description; - mGroupId = groupId; - mExtraData = new JSONObject(); - } - - /** - * Fetches the request URL. - * The request URL is the query sent to the Physical Web Service and should be identical to the - * URL broadcasted by the device. - * @return The request URL - */ - public String getRequestUrl() { - return mRequestUrl; - } - - /** - * Fetches the site URL. - * The site URL is returned by the Physical Web Service. It may differ from the request URL if - * a redirector or URL shortener was used. - * @return The site URL - */ - public String getSiteUrl() { - return mSiteUrl; - } - - /** - * Fetches the title. - * The title is parsed from the title tag of the web page. - * @return The title - */ - public String getTitle() { - return mTitle; - } - - /** - * Fetches the description. - * The description is a snippet of text describing the contents of the web page. - * @return The description - */ - public String getDescription() { - return mDescription; - } - - /** - * Check if we have an icon URL. - * @return whether this object has an icon URL. - */ - public boolean hasIconUrl() { - return mIconUrl != null; - } - - /** - * Fetches the icon URL. - * The icon URL is returned by the Physical Web Service. - * @return The icon URL - */ - public String getIconUrl() { - return mIconUrl; - } - - /** - * Check if we have a group id. - * @return whether this object has a group id. - */ - public boolean hasGroupId() { - return mGroupId != null; - } - - /** - * Fetches the URL group ID. - * The group ID is returned by the Physical Web Service and is used to group similar (but not - * necessarily identical) URLs. - * @return The URL group ID, may be null - */ - public String getGroupId() { - return mGroupId; - } - - /** - * Get extra data JSONObject. - * This is where client code should store custom data. - * @return Extra data. - */ - public JSONObject getExtraData() { - return mExtraData; - } - - /** - * Create a JSON object that represents this data structure. - * @return a JSON serialization of this data structure. - */ - public JSONObject jsonSerialize() { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(REQUESTURL_KEY, mRequestUrl); - jsonObject.put(SITEURL_KEY, mSiteUrl); - jsonObject.put(TITLE_KEY, mTitle); - jsonObject.put(DESCRIPTION_KEY, mDescription); - - if (mIconUrl != null) { - jsonObject.put(ICONURL_KEY, mIconUrl); - } - - if (mGroupId != null) { - jsonObject.put(GROUPID_KEY, mGroupId); - } - - if (mExtraData.length() > 0) { - jsonObject.put(EXTRA_KEY, mExtraData); - } - - return jsonObject; - } - - /** - * Populate a PwsResult with data from a given JSON object. - * @param jsonObject a serialized PwsResult. - * @return The PwsResult represented by the serialized object. - */ - public static PwsResult jsonDeserialize(JSONObject jsonObject) { - String requestUrl = jsonObject.getString(REQUESTURL_KEY); - String siteUrl = jsonObject.getString(SITEURL_KEY); - String title = jsonObject.getString(TITLE_KEY); - String description = jsonObject.getString(DESCRIPTION_KEY); - String iconUrl = null; - if (jsonObject.has(ICONURL_KEY)) { - iconUrl = jsonObject.getString(ICONURL_KEY); - } - String groupId = null; - if (jsonObject.has(GROUPID_KEY)) { - groupId = jsonObject.getString(GROUPID_KEY); - } - - PwsResult pwsResult = new PwsResult(requestUrl, siteUrl, title, description, iconUrl, groupId); - if (jsonObject.has(EXTRA_KEY)) { - pwsResult.mExtraData = jsonObject.getJSONObject(EXTRA_KEY); - } - return pwsResult; - } - - /** - * Return a hash code for this PwsResult. - * This calculation does not include the extra data. - * @return hash code - */ - @Override - public int hashCode() { - int hash = 1; - hash = hash * 31 + mRequestUrl.hashCode(); - hash = hash * 31 + mSiteUrl.hashCode(); - hash = hash * 31 + mTitle.hashCode(); - hash = hash * 31 + mDescription.hashCode(); - hash = hash * 31 + mIconUrl.hashCode(); - hash = hash * 31 + ((mGroupId == null) ? 0 : mGroupId.hashCode()); - return hash; - } - - /** - * Check if two PwsResults are equal. - * This does not compare extra data. - * @param other the PwsResult to compare to. - * @return true if the PwsResults are equal. - */ - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (other instanceof PwsResult) { - PwsResult pwsResult = (PwsResult) other; - return compareTo(pwsResult) == 0; - } - return false; - } - - /** - * Compare two PwsResults based on request URL alphabetical ordering, breaking ties by comparing - * site URL and group ID. - * @param other the PwsResult to compare to. - * @return the comparison value. - */ - @Override - public int compareTo(PwsResult other) { - if (this == other) { - return 0; - } - - // 1. mRequestUrl - int compareValue = Utils.nullSafeCompare(mRequestUrl, other.mRequestUrl); - if (compareValue != 0) { - return compareValue; - } - - // 2. mSiteUrl - compareValue = Utils.nullSafeCompare(mSiteUrl, other.mSiteUrl); - if (compareValue != 0) { - return compareValue; - } - - // 3. mTitle - compareValue = Utils.nullSafeCompare(mTitle, other.mTitle); - if (compareValue != 0) { - return compareValue; - } - - // 4. mDescription - compareValue = Utils.nullSafeCompare(mDescription, other.mDescription); - if (compareValue != 0) { - return compareValue; - } - - // 5. mIconUrl - compareValue = Utils.nullSafeCompare(mIconUrl, other.mIconUrl); - if (compareValue != 0) { - return compareValue; - } - - // 6. mGroupId - return Utils.nullSafeCompare(mGroupId, other.mGroupId); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PwsResultCallback.java b/java/libs/src/main/java/org/physical_web/collection/PwsResultCallback.java deleted file mode 100644 index 67ddbf23..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PwsResultCallback.java +++ /dev/null @@ -1,50 +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.collection; - -import java.util.Collection; - -/** - * Callback to implement for requests made to the Physical Web Service. - */ -public abstract class PwsResultCallback { - /** - * Handles a valid PwsResult. - * @param pwsResult The result returned from the Physical Web Service. - */ - public abstract void onPwsResult(PwsResult pwsResult); - - /** - * Handles a URL that did not receive a result from the PWS. - * @param url The URL sent to the Physical Web Service. - */ - public void onPwsResultAbsent(String url) {} - - /** - * Handles an error that occurred while attempting to use the Physical Web Service. - * @param urls The urls sent in batch to the Physical Web Service. - * @param httpResponseCode The HTTP response code returned by the Physical - * Web Service. This will be 0 if a response was never received. - * @param e The encountered exception. - */ - public void onPwsResultError(Collection urls, int httpResponseCode, Exception e) {} - - /** - * Handles error that occurred while attempting to use the Physical Web Service. - * @param durationMillis The number of milliseconds it took to receive a response. - */ - public void onResponseReceived(long durationMillis) {} -} diff --git a/java/libs/src/main/java/org/physical_web/collection/PwsResultIconCallback.java b/java/libs/src/main/java/org/physical_web/collection/PwsResultIconCallback.java deleted file mode 100644 index 6569fe42..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/PwsResultIconCallback.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2016 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.collection; - -/** - * Callback to implement for requests made to fetch icons. - */ -public abstract class PwsResultIconCallback { - /** - * Handle a valid PwsResult. - * @param icon The icon returned from the HTTP request. - */ - public abstract void onIcon(byte[] icon); - - /** - * Handle an error that occurred while attempting to use the Physical Web - * Service. - * @param httpResponseCode The HTTP response code returned by the Physical - * Web Service. This will be 0 if a response was never received. - * @param e The encountered exception. - */ - public void onError(int httpResponseCode, Exception e) {} -} diff --git a/java/libs/src/main/java/org/physical_web/collection/Request.java b/java/libs/src/main/java/org/physical_web/collection/Request.java deleted file mode 100644 index 74bf0b87..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/Request.java +++ /dev/null @@ -1,113 +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.collection; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -/** - * A class that represents an http request. - * This is to be used as a base class for more specific request classes. - * @param The type representing the request payload. - */ -abstract class Request extends Thread { - private URL mUrl; - private RequestCallback mCallback; - - /** - * Construct a Request object. - * @param url The url to make an HTTP request to. - * @param callback The callback run when the HTTP response is received. - * @throws MalformedURLException on invalid url - */ - public Request(String url, RequestCallback callback) throws MalformedURLException { - mUrl = new URL(url); - mCallback = callback; - } - - /** - * The callback that gets run after the request is made. - */ - public interface RequestCallback { - /** - * The callback run on a valid response. - * @param result The result object. - */ - void onResponse(T result); - - /** - * The callback run on an Exception. - * @param httpResponseCode The HTTP response code. This will be 0 if no - * response was received. - * @param e The encountered Exception. - */ - void onError(int httpResponseCode, Exception e); - } - - /** - * Make the HTTP request and parse the HTTP response. - */ - @Override - public void run() { - // Setup some values - HttpURLConnection urlConnection = null; - T result = null; - InputStream inputStream = null; - int responseCode = 0; - IOException ioException = null; - - // Make the request - try { - urlConnection = (HttpURLConnection) mUrl.openConnection(); - writeToUrlConnection(urlConnection); - responseCode = urlConnection.getResponseCode(); - inputStream = new BufferedInputStream(urlConnection.getInputStream()); - result = readInputStream(inputStream); - } catch (IOException e) { - ioException = e; - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - - // Call the callback - if (ioException == null) { - mCallback.onResponse(result); - } else { - mCallback.onError(responseCode, ioException); - } - } - - /** - * Helper method to make an HTTP request. - * @param urlConnection The HTTP connection. - * @throws IOException on error - */ - protected abstract void writeToUrlConnection(HttpURLConnection urlConnection) throws IOException; - - /** - * Helper method to read an HTTP response. - * @param is The InputStream. - * @return An object representing the HTTP response. - * @throws IOException on error - */ - protected abstract T readInputStream(InputStream is) throws IOException; -} diff --git a/java/libs/src/main/java/org/physical_web/collection/UrlDevice.java b/java/libs/src/main/java/org/physical_web/collection/UrlDevice.java deleted file mode 100644 index b02f6d27..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/UrlDevice.java +++ /dev/null @@ -1,159 +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.collection; - -import org.json.JSONObject; - -/** - * The class defining a Physical Web URL device. - */ -public class UrlDevice { - private static final String ID_KEY = "id"; - private static final String URL_KEY = "url"; - private static final String EXTRA_KEY = "extra"; - private String mId; - private String mUrl; - private JSONObject mExtraData; - - /** - * Construct a SimpleUrlDevice. - * @param id The id of the device. - * @param url The URL broadcasted by the device. - */ - public UrlDevice(String id, String url) { - mId = id; - mUrl = url; - mExtraData = new JSONObject(); - } - - /** - * Fetches the ID of the device. - * The ID should be unique across UrlDevices. This should even be the case when - * one real world device is broadcasting multiple URLs. - * @return The ID of the device. - */ - public String getId() { - return mId; - } - - /** - * Fetches the URL broadcasted by the device. - * @return The broadcasted URL. - */ - public String getUrl() { - return mUrl; - } - - /** - * Returns the rank of this device given its associated PwsResult. - * @param pwsResult is the response received from the Physical Web Service - * for the url broadcasted by this UrlDevice. - * @return .5 (at the moment we don't have anything by which to judge rank) - * TODO(cco3): Move ranking outside of this class - */ - public double getRank(PwsResult pwsResult) { - return .5; - } - - /** - * Get extra data JSONObject. - * This is where client code should store custom data. - * @return Extra data. - */ - public JSONObject getExtraData() { - return mExtraData; - } - - /** - * Create a JSON object that represents this data structure. - * @return a JSON serialization of this data structure. - */ - public JSONObject jsonSerialize() { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(ID_KEY, mId); - jsonObject.put(URL_KEY, mUrl); - - if (mExtraData.length() > 0) { - jsonObject.put(EXTRA_KEY, mExtraData); - } - - return jsonObject; - } - - /** - * Populate a UrlDevice with data from a given JSON object. - * @param jsonObject a serialized UrlDevice. - * @return The UrlDevice represented by the serialized object. - */ - public static UrlDevice jsonDeserialize(JSONObject jsonObject) { - String id = jsonObject.getString(ID_KEY); - String url = jsonObject.getString(URL_KEY); - - UrlDevice urlDevice = new UrlDevice(id, url); - if (jsonObject.has(EXTRA_KEY)) { - urlDevice.mExtraData = jsonObject.getJSONObject(EXTRA_KEY); - } - return urlDevice; - } - - /** - * Return a hash code for this SimpleUrlDevice. - * This calculation does not include the extra data. - * @return hash code - */ - public int hashCode() { - int hash = 1; - hash = hash * 31 + mId.hashCode(); - hash = hash * 31 + mUrl.hashCode(); - return hash; - } - - /** - * Check if two UrlDevices are equal. - * This does not compare extra data. - * @param other the UrlDevice to compare to. - * @return true if the UrlDevices are equal - */ - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (other instanceof UrlDevice) { - UrlDevice urlDevice = (UrlDevice) other; - return compareTo(urlDevice) == 0; - } - return false; - } - - /** - * Compare two UrlDevices based on device ID, breaking ties by comparing broadcast URL. - * @param other the UrlDevice to compare to. - * @return the comparison value. - */ - public int compareTo(UrlDevice other) { - if (this == other) { - return 0; - } - - int compareValue = mId.compareTo(other.getId()); - if (compareValue != 0) { - return compareValue; - } - - return mUrl.compareTo(other.getUrl()); - } -} diff --git a/java/libs/src/main/java/org/physical_web/collection/UrlGroup.java b/java/libs/src/main/java/org/physical_web/collection/UrlGroup.java deleted file mode 100644 index bf3173f0..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/UrlGroup.java +++ /dev/null @@ -1,122 +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.collection; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.PriorityQueue; - -/** - * A collection of similar URLs, the devices broadcasting those URLs, and associated metadata. - */ -public class UrlGroup implements Comparable { - private String mGroupId; - private PriorityQueue mPwPairs; - - /** - * Construct a UrlGroup. - * @param groupId The URL group ID of this group. - */ - public UrlGroup(String groupId) { - mGroupId = groupId; - mPwPairs = new PriorityQueue<>(1, Collections.reverseOrder()); - } - - /** - * Gets the group ID for this group. - * @return Group ID - */ - public String getGroupId() { - return mGroupId; - } - - /** - * Add a PwPair to this group. - * @param pwPair The PwPair to add. - */ - public void addPair(PwPair pwPair) { - mPwPairs.add(pwPair); - } - - /** - * Get the top-ranked PwPair in this group. - * @return The top PwPair. - */ - public PwPair getTopPair() { - return mPwPairs.peek(); - } - - /** - * Return a hash code for this UrlGroup. - * @return hash code - */ - @Override - public int hashCode() { - int hash = 1; - hash = hash * 31 + ((mGroupId == null) ? 0 : mGroupId.hashCode()); - hash = hash * 31 + mPwPairs.hashCode(); - return hash; - } - - /** - * Check if two UrlGroups are equal. - * @param other the UrlGroup to compare to. - * @return true if the top pairs are of equal rank. - */ - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (other instanceof UrlGroup) { - UrlGroup otherUrlGroup = (UrlGroup) other; - if (mPwPairs.size() == otherUrlGroup.mPwPairs.size() && - mGroupId.equals(otherUrlGroup.mGroupId)) { - // don't consider order when comparing lists - List myPairs = new ArrayList<>(mPwPairs); - List otherPairs = new ArrayList<>(otherUrlGroup.mPwPairs); - Collections.sort(myPairs); - Collections.sort(otherPairs); - return myPairs.equals(otherPairs); - } - } - - return false; - } - - /** - * Compare two UrlGroups based on the ranks of their top pairs. - * Ties are broken by alphabetical comparison of groupid strings. - * @param other the UrlGroup to compare to. - * @return the comparison value. - */ - @Override - public int compareTo(UrlGroup other) { - if (this == other) { - return 0; - } - - int compareValue = Utils.nullSafeCompare(getTopPair(), other.getTopPair()); - if (compareValue != 0) { - return compareValue; - } - - return Utils.nullSafeCompare(mGroupId, other.mGroupId); - } - -} diff --git a/java/libs/src/main/java/org/physical_web/collection/Utils.java b/java/libs/src/main/java/org/physical_web/collection/Utils.java deleted file mode 100644 index 4b414cc6..00000000 --- a/java/libs/src/main/java/org/physical_web/collection/Utils.java +++ /dev/null @@ -1,41 +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.collection; - -/** - * Utility methods for the Physical Web library. - */ -class Utils { - /** - * Compare two nullable comparables, preferring any non-null value over null. - * @param o1 first object to compare - * @param o2 second object to compare - * @param a type that implements Comparable<T> - * @return comparison value - */ - public static > int nullSafeCompare(T o1, T o2) { - if (o1 == o2) { - return 0; - } - if (o1 == null) { - return -1; - } - if (o2 == null) { - return 1; - } - return o1.compareTo(o2); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/PhysicalWebCollectionTest.java b/java/libs/src/test/java/org/physical_web/collection/PhysicalWebCollectionTest.java deleted file mode 100644 index 81bd5f20..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/PhysicalWebCollectionTest.java +++ /dev/null @@ -1,173 +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.collection; - -import static org.junit.Assert.*; - -import org.json.JSONObject; - -import org.junit.Before; -import org.junit.Test; - -import org.skyscreamer.jsonassert.JSONAssert; - -import java.util.List; - -/** - * PhysicalWebCollection unit test class. - */ -@SuppressWarnings("PMD.AvoidDuplicateLiterals") -public class PhysicalWebCollectionTest { - private static final String ID1 = "id1"; - private static final String ID2 = "id2"; - private static final String ID3 = "id3"; - private static final String ID4 = "id4"; - private static final String ID5 = "id5"; - private static final String URL1 = "http://example.com"; - private static final String URL2 = "http://physical-web.org"; - private static final String URL3a = "http://example.com/#a"; - private static final String URL3b = "http://example.com/#b"; - private static final String TITLE1 = "title1"; - private static final String TITLE2 = "title2"; - private static final String DESCRIPTION1 = "description1"; - private static final String DESCRIPTION2 = "description2"; - private static final String ICON_URL1 = "http://example.com/favicon.ico"; - private static final String ICON_URL2 = "http://physical-web.org/favicon.ico"; - private static final String GROUP_ID1 = "group1"; - private static final String GROUP_ID2 = "group2"; - private PhysicalWebCollection physicalWebCollection1; - private JSONObject jsonObject1; - - private void addRankedDeviceAndMetadata(PhysicalWebCollection collection, String id, String url, - String groupId, double rank) { - PwPair rankedPair = RankedDevice.createRankedPair(id, url, groupId, rank); - collection.addUrlDevice(rankedPair.getUrlDevice()); - collection.addMetadata(rankedPair.getPwsResult()); - } - - @Before - public void setUp() { - physicalWebCollection1 = new PhysicalWebCollection(); - UrlDevice urlDevice = new UrlDevice(ID1, URL1); - PwsResult pwsResult = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - physicalWebCollection1.addUrlDevice(urlDevice); - physicalWebCollection1.addMetadata(pwsResult); - jsonObject1 = new JSONObject("{" - + " \"schema\": 1," - + " \"devices\": [{" - + " \"id\": \"" + ID1 + "\"," - + " \"url\": \"" + URL1 + "\"" - + " }]," - + " \"metadata\": [{" - + " \"requesturl\": \"" + URL1 + "\"," - + " \"siteurl\": \"" + URL1 + "\"," - + " \"title\": \"" + TITLE1 + "\"," - + " \"description\": \"" + DESCRIPTION1 + "\"," - + " \"iconurl\": \"" + ICON_URL1 + "\"," - + " \"groupid\": \"" + GROUP_ID1 + "\"" - + " }]" - + "}"); - } - - @Test - public void getUrlDeviceByIdReturnsFoundUrlDevice() { - UrlDevice urlDevice = physicalWebCollection1.getUrlDeviceById(ID1); - assertEquals(urlDevice.getId(), ID1); - assertEquals(urlDevice.getUrl(), URL1); - } - - @Test - public void getUrlDeviceByIdReturnsNullForMissingUrlDevice() { - UrlDevice fetchedUrlDevice = physicalWebCollection1.getUrlDeviceById(ID2); - assertNull(fetchedUrlDevice); - } - - @Test - public void getMetadataByRequestUrlReturnsFoundMetadata() { - PwsResult pwsResult = physicalWebCollection1.getMetadataByBroadcastUrl(URL1); - assertEquals(pwsResult.getRequestUrl(), URL1); - assertEquals(pwsResult.getSiteUrl(), URL1); - assertEquals(pwsResult.getGroupId(), GROUP_ID1); - } - - @Test - public void getMetadataByRequestUrlReturnsNullForMissingMetadata() { - PwsResult pwsResult = physicalWebCollection1.getMetadataByBroadcastUrl(URL2); - assertNull(pwsResult); - } - - @Test - public void jsonSerializeWorks() { - JSONAssert.assertEquals(physicalWebCollection1.jsonSerialize(), jsonObject1, true); - } - - @Test - public void jsonDeserializeWorks() throws PhysicalWebCollectionException { - PhysicalWebCollection physicalWebCollection = - PhysicalWebCollection.jsonDeserialize(jsonObject1); - UrlDevice urlDevice = physicalWebCollection.getUrlDeviceById(ID1); - PwsResult pwsResult = physicalWebCollection.getMetadataByBroadcastUrl(URL1); - assertNotNull(urlDevice); - assertEquals(urlDevice.getId(), ID1); - assertEquals(urlDevice.getUrl(), URL1); - assertNotNull(pwsResult); - assertEquals(pwsResult.getRequestUrl(), URL1); - assertEquals(pwsResult.getSiteUrl(), URL1); - assertEquals(pwsResult.getGroupId(), GROUP_ID1); - } - - @Test - public void getPwPairsSortedByRankWorks() { - PhysicalWebCollection physicalWebCollection = new PhysicalWebCollection(); - addRankedDeviceAndMetadata(physicalWebCollection, ID1, URL1, null, .1); - addRankedDeviceAndMetadata(physicalWebCollection, ID2, URL2, null, .5); - addRankedDeviceAndMetadata(physicalWebCollection, ID3, URL2, null, .9); // Duplicate URL - List pwPairs = physicalWebCollection.getPwPairsSortedByRank(); - assertEquals(pwPairs.size(), 2); - assertEquals(pwPairs.get(0).getUrlDevice().getId(), ID3); - assertEquals(pwPairs.get(1).getUrlDevice().getId(), ID1); - } - - @Test - public void getGroupedPwPairsSortedByRankWorks() { - PhysicalWebCollection physicalWebCollection = new PhysicalWebCollection(); - addRankedDeviceAndMetadata(physicalWebCollection, ID1, URL1, GROUP_ID1, .1); // Group 1 - addRankedDeviceAndMetadata(physicalWebCollection, ID2, URL2, null, .6); // Ungrouped - addRankedDeviceAndMetadata(physicalWebCollection, ID3, URL2, null, .7); // Duplicate URL - addRankedDeviceAndMetadata(physicalWebCollection, ID4, URL3a, GROUP_ID2, .5); // Group 2 - addRankedDeviceAndMetadata(physicalWebCollection, ID5, URL3b, GROUP_ID2, .9); // Also group 2 - List groupedPairs = physicalWebCollection.getGroupedPwPairsSortedByRank(); - assertEquals(groupedPairs.size(), 3); - assertEquals(groupedPairs.get(0).getPwsResult().getGroupId(), GROUP_ID2); - assertEquals(groupedPairs.get(0).getUrlDevice().getId(), ID5); - assertEquals(groupedPairs.get(1).getPwsResult().getGroupId(), null); - assertEquals(groupedPairs.get(1).getUrlDevice().getId(), ID3); - assertEquals(groupedPairs.get(2).getPwsResult().getGroupId(), GROUP_ID1); - assertEquals(groupedPairs.get(2).getUrlDevice().getId(), ID1); - } - - @Test - public void getTopRankedPwPairByGroupIdWorks() { - PhysicalWebCollection physicalWebCollection = new PhysicalWebCollection(); - addRankedDeviceAndMetadata(physicalWebCollection, ID1, URL1, GROUP_ID1, .1); // Group 1 - addRankedDeviceAndMetadata(physicalWebCollection, ID2, URL2, GROUP_ID1, .2); // Better rank - addRankedDeviceAndMetadata(physicalWebCollection, ID1, URL1, GROUP_ID2, .3); // Group 2 - assertNull(physicalWebCollection.getTopRankedPwPairByGroupId("notagroup")); - PwPair pwPair = physicalWebCollection.getTopRankedPwPairByGroupId(GROUP_ID1); - assertNotNull(pwPair); - assertEquals(ID2, pwPair.getUrlDevice().getId()); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/PwPairTest.java b/java/libs/src/test/java/org/physical_web/collection/PwPairTest.java deleted file mode 100644 index 7dcdc4dd..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/PwPairTest.java +++ /dev/null @@ -1,119 +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.collection; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; - -/** - * PwPair unit test class. - */ -public class PwPairTest { - private static final String ID1 = "id1"; - private static final String ID2 = "id2"; - private static final String URL1 = "http://example.com"; - private static final String URL2 = "http://physical-web.org"; - private static final String TITLE1 = "title1"; - private static final String TITLE2 = "title2"; - private static final String DESCRIPTION1 = "description1"; - private static final String DESCRIPTION2 = "description2"; - private static final String ICON_URL1 = "http://example.com/favicon.ico"; - private static final String ICON_URL2 = "http://physical-web.org/favicon.ico"; - private static final String GROUP_ID1 = "group1"; - private static final String GROUP_ID2 = "group2"; - private static final double RANK1 = 0.5d; - private static final double RANK2 = 0.9d; - private UrlDevice mUrlDevice1; - private PwsResult mPwsResult1; - private PwPair mPwPair1; - - @Before - public void setUp() { - mUrlDevice1 = new UrlDevice(ID1, URL1); - mPwsResult1 = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - mPwPair1 = new PwPair(mUrlDevice1, mPwsResult1); - } - - @Test - public void constructorCreatesProperObject() { - assertEquals(mUrlDevice1, mPwPair1.getUrlDevice()); - assertEquals(mPwsResult1, mPwPair1.getPwsResult()); - } - - @Test - public void pairIsEqualToItself() { - assertEquals(mPwPair1, mPwPair1); - } - - @Test - public void alikePairsAreEqual() { - UrlDevice urlDevice2 = new UrlDevice(ID1, URL1); - PwsResult pwsResult2 = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - UrlDevice urlDevice3 = new RankedDevice(ID1, URL1, RANK1); - PwPair pwPair2 = new PwPair(urlDevice2, pwsResult2); // identical PwPair - PwPair pwPair3 = new PwPair(urlDevice3, mPwsResult1); // same info, but uses a RankedDevice - assertEquals(mPwPair1, pwPair2); - assertEquals(mPwPair1, pwPair3); - } - - @Test - public void unalikePairsAreNotEqual() { - PwsResult pwsResult2 = new PwsResult(URL1, URL2, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - UrlDevice urlDevice2 = new UrlDevice(ID2, URL1); - UrlDevice urlDevice3 = new RankedDevice(ID1, URL1, RANK2); - PwPair pwPair2 = new PwPair(mUrlDevice1, pwsResult2); // different URL metadata - PwPair pwPair3 = new PwPair(urlDevice2, mPwsResult1); // different device - PwPair pwPair4 = new PwPair(urlDevice3, mPwsResult1); // different rank - assertNotEquals(mPwPair1, pwPair2); - assertNotEquals(mPwPair1, pwPair3); - assertNotEquals(mPwPair1, pwPair4); - } - - @Test - public void comparePairToItselfReturnsZero() { - assertEquals(mPwPair1.compareTo(mPwPair1), 0); - } - - @Test - public void comparePairToAlikePairReturnsZero() { - UrlDevice urlDevice2 = new UrlDevice(ID1, URL1); - PwsResult pwsResult2 = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - UrlDevice urlDevice3 = new RankedDevice(ID1, URL1, RANK1); - PwPair pwPair2 = new PwPair(urlDevice2, pwsResult2); // identical PwPair - PwPair pwPair3 = new PwPair(urlDevice3, mPwsResult1); // same info, but uses a RankedDevice - assertEquals(mPwPair1.compareTo(pwPair2), 0); - assertEquals(mPwPair1.compareTo(pwPair3), 0); - assertEquals(pwPair3.compareTo(mPwPair1), 0); // exercise null checks with reverse compare - } - - @Test - public void comparePairToUnalikePairReturnsNonZero() { - UrlDevice urlDevice2 = new UrlDevice(ID2, URL1); - UrlDevice urlDevice3 = new RankedDevice(ID1, URL1, RANK2); - PwsResult pwsResult2 = new PwsResult(URL2, URL2, TITLE1, DESCRIPTION1, ICON_URL2, GROUP_ID2); - PwPair pwPair2 = new PwPair(urlDevice2, mPwsResult1); - PwPair pwPair3 = new PwPair(urlDevice3, mPwsResult1); - PwPair pwPair4 = new PwPair(mUrlDevice1, pwsResult2); - assertTrue(mPwPair1.compareTo(pwPair2) < 0); // "ID1" < "ID2" - assertTrue(mPwPair1.compareTo(pwPair3) < 0); // 0.5 < 0.9 - assertTrue(mPwPair1.compareTo(pwPair4) < 0); // "example.com" < null "physical-web.org" - assertTrue(pwPair2.compareTo(mPwPair1) > 0); - assertTrue(pwPair3.compareTo(mPwPair1) > 0); - assertTrue(pwPair4.compareTo(mPwPair1) > 0); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/PwsResultTest.java b/java/libs/src/test/java/org/physical_web/collection/PwsResultTest.java deleted file mode 100644 index 747f1929..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/PwsResultTest.java +++ /dev/null @@ -1,160 +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.collection; - -import static org.junit.Assert.*; - -import org.json.JSONObject; - -import org.junit.Before; -import org.junit.Test; - -import org.skyscreamer.jsonassert.JSONAssert; - -/** - * PwsResult unit test class. - */ -public class PwsResultTest { - private static final String URL1 = "http://example.com"; - private static final String URL2 = "http://physical-web.org"; - private static final String TITLE1 = "title1"; - private static final String TITLE2 = "title2"; - private static final String DESCRIPTION1 = "description1"; - private static final String DESCRIPTION2 = "description2"; - private static final String ICON_URL1 = "http://example.com/favicon.ico"; - private static final String ICON_URL2 = "http://physical-web.org/favicon.ico"; - private static final String GROUP_ID1 = "group1"; - private static final String GROUP_ID2 = "group2"; - private PwsResult mPwsResult1 = null; - private JSONObject jsonObject1 = null; - - @Before - public void setUp() { - mPwsResult1 = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - mPwsResult1.getExtraData().put("key", "value"); - String endline = "\","; - jsonObject1 = new JSONObject("{" - + " \"requesturl\": \"" + URL1 + endline - + " \"siteurl\": \"" + URL1 + endline - + " \"title\": \"" + TITLE1 + endline - + " \"description\": \"" + DESCRIPTION1 + endline - + " \"iconurl\": \"" + ICON_URL1 + endline - + " \"groupid\": \"" + GROUP_ID1 + endline - + " \"extra\": {" - + " \"key\": \"value\"" - + " }" - + "}"); - } - - @Test - public void constructorCreatesProperObject() { - assertEquals(mPwsResult1.getRequestUrl(), URL1); - assertEquals(mPwsResult1.getSiteUrl(), URL1); - assertEquals(mPwsResult1.getTitle(), TITLE1); - assertEquals(mPwsResult1.getDescription(), DESCRIPTION1); - assertEquals(mPwsResult1.getIconUrl(), ICON_URL1); - assertEquals(mPwsResult1.getGroupId(), GROUP_ID1); - } - - @Test - public void getExtraDataWorks() { - assertEquals(1, mPwsResult1.getExtraData().length()); - } - - @Test - public void jsonSerializeWorks() { - JSONAssert.assertEquals(mPwsResult1.jsonSerialize(), jsonObject1, true); - } - - @Test - public void jsonDeserializeWorks() throws PhysicalWebCollectionException { - PwsResult pwsResult = PwsResult.jsonDeserialize(jsonObject1); - assertNotNull(pwsResult); - assertEquals(pwsResult.getRequestUrl(), URL1); - assertEquals(pwsResult.getSiteUrl(), URL1); - assertEquals(pwsResult.getGroupId(), GROUP_ID1); - } - - @Test - public void jsonSerializeAndDeserializePreservesNullValues() throws Exception { - PwsResult pwsResult = - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, null, null); // null values - pwsResult = PwsResult.jsonDeserialize(pwsResult.jsonSerialize()); - assertNull(pwsResult.getIconUrl()); - assertNull(pwsResult.getGroupId()); - } - - @Test - public void resultIsEqualToItself() { - assertEquals(mPwsResult1, mPwsResult1); - } - - @Test - public void alikeResultsAreEqual() { - PwsResult pwsResult = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - pwsResult.getExtraData().put("key", "value"); - assertEquals(mPwsResult1, pwsResult); - } - - @Test - public void unalikeResultsAreNotEqual() { - assertNotEquals(mPwsResult1, - new PwsResult(URL2, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1)); - assertNotEquals(mPwsResult1, - new PwsResult(URL1, URL2, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1)); - assertNotEquals(mPwsResult1, - new PwsResult(URL1, URL1, TITLE2, DESCRIPTION1, ICON_URL1, GROUP_ID1)); - assertNotEquals(mPwsResult1, - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION2, ICON_URL1, GROUP_ID1)); - assertNotEquals(mPwsResult1, - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL2, GROUP_ID1)); - assertNotEquals(mPwsResult1, - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID2)); - } - - @Test - public void compareResultToItselfReturnsZero() { - assertEquals(mPwsResult1.compareTo(mPwsResult1), 0); - } - - @Test - public void compareResultToAlikeResultReturnsZero() { - PwsResult pwsResult = new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1); - assertEquals(0, mPwsResult1.compareTo(pwsResult)); - } - - @Test - public void compareResultToUnalikeResultReturnsNonZero() { - // "example.com" < "physical-web.org" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL2, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1)) < 0); - // "example.com" < "physical-web.org" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL1, URL2, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID1)) < 0); - // "title1" < "title2" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL1, URL1, TITLE2, DESCRIPTION1, ICON_URL1, GROUP_ID1)) < 0); - // "description1" < "description2" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION2, ICON_URL1, GROUP_ID1)) < 0); - // "example.com/favicon.ico" < "physical-web.org/favicon.ico" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL2, GROUP_ID1)) < 0); - // "group1" < "group2" - assertTrue(mPwsResult1.compareTo( - new PwsResult(URL1, URL1, TITLE1, DESCRIPTION1, ICON_URL1, GROUP_ID2)) < 0); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/RankedDevice.java b/java/libs/src/test/java/org/physical_web/collection/RankedDevice.java deleted file mode 100644 index 68432d9b..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/RankedDevice.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.physical_web.collection; - -/** - * A mock UrlDevice with a configurable rank value. - */ -public class RankedDevice extends UrlDevice { - private double mRank; - - public RankedDevice(String id, String url, double rank) { - super(id, url); - mRank = rank; - } - - @Override - public double getRank(PwsResult pwsResult) { - return mRank; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object other) { - return super.equals(other); - } - - /** - * Utility method for constructing a PwPair with a RankedDevice. - * @param id URL device ID - * @param url Broadcast URL - * @param groupId URL group ID - * @param rank Rank value for this device - * @return New PwPair - */ - public static PwPair createRankedPair(String id, String url, String groupId, double rank) { - UrlDevice urlDevice = new RankedDevice(id, url, rank); - PwsResult pwsResult = new PwsResult(url, url, "title1", "description1", null, groupId); - return new PwPair(urlDevice, pwsResult); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/UrlDeviceTest.java b/java/libs/src/test/java/org/physical_web/collection/UrlDeviceTest.java deleted file mode 100644 index 17986046..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/UrlDeviceTest.java +++ /dev/null @@ -1,152 +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.collection; - -import static org.junit.Assert.*; - -import org.json.JSONObject; - -import org.junit.Before; -import org.junit.Test; - -import org.skyscreamer.jsonassert.JSONAssert; - -/** - * SimpleUrlDevice unit test class. - */ -public class UrlDeviceTest { - private static final String ID1 = "id1"; - private static final String ID2 = "id2"; - private static final String URL1 = "http://example.com"; - private static final String URL2 = "http://physical-web.org"; - private static final double RANK1 = 0.5d; - private static final double RANK2 = 0.9d; - private UrlDevice mUrlDevice1; - private JSONObject jsonObject1; - - @Before - public void setUp() { - mUrlDevice1 = new UrlDevice(ID1, URL1); - mUrlDevice1.getExtraData().put("key", "value"); - jsonObject1 = new JSONObject("{" - + " \"id\": \"" + ID1 + "\"," - + " \"url\": \"" + URL1 + "\"," - + " \"extra\": {" - + " \"key\": \"value\"" - + " }" - + "}"); - } - - @Test - public void getIdReturnsId() { - assertEquals(mUrlDevice1.getId(), ID1); - } - - @Test - public void getUrlReturnsUrl() { - assertEquals(mUrlDevice1.getUrl(), URL1); - } - - @Test - public void getRankReturnsPointFive() { - PwsResult pwsResult = new PwsResult(URL1, URL1, "title1", "description1", null, null); - assertEquals(.5, mUrlDevice1.getRank(pwsResult), .0001); - } - - @Test - public void getExtraDataWorks() { - assertEquals(1, mUrlDevice1.getExtraData().length()); - } - - @Test - public void jsonSerializeWorks() { - JSONAssert.assertEquals(mUrlDevice1.jsonSerialize(), jsonObject1, true); - } - - @Test - public void jsonDeserializeWorks() { - UrlDevice urlDevice = UrlDevice.jsonDeserialize(jsonObject1); - assertNotNull(urlDevice); - assertEquals(urlDevice.getId(), ID1); - assertEquals(urlDevice.getUrl(), URL1); - } - - @Test - public void deviceIsEqualToItself() { - assertEquals(mUrlDevice1, mUrlDevice1); - } - - @Test - public void alikeDevicesAreEqual() { - UrlDevice urlDevice = new UrlDevice(ID1, URL1); - urlDevice.getExtraData().put("key", "value"); - assertEquals(mUrlDevice1, urlDevice); - } - - @Test - public void devicesWithDifferentClassButSameInfoAreEqual() { - UrlDevice simpleUrlDevice = new UrlDevice(ID1, URL1); - UrlDevice rankedDevice = new RankedDevice(ID1, URL1, RANK1); - assertEquals(rankedDevice, simpleUrlDevice); - assertEquals(simpleUrlDevice, rankedDevice); - } - - @Test - public void devicesWithDifferentRankButSameInfoAreEqual() { - UrlDevice urlDevice = new RankedDevice(ID1, URL1, RANK2); // different rank - urlDevice.getExtraData().put("key", "value"); - assertEquals(mUrlDevice1, urlDevice); // equals should not consider rank - } - - @Test - public void unalikeDevicesAreNotEqual() { - UrlDevice urlDevice2 = new UrlDevice(ID1, URL2); // same id, different url - UrlDevice urlDevice3 = new UrlDevice(ID2, URL1); // same url, different id - assertNotEquals(mUrlDevice1, urlDevice2); - assertNotEquals(mUrlDevice1, urlDevice3); - } - - @Test - public void compareDeviceToItselfReturnsZero() { - assertEquals(mUrlDevice1.compareTo(mUrlDevice1), 0); - } - - @Test - public void compareDeviceToAlikeDeviceReturnsZero() { - UrlDevice urlDevice2 = new UrlDevice(ID1, URL1); - UrlDevice urlDevice3 = new RankedDevice(ID1, URL1, RANK1); - assertEquals(mUrlDevice1.compareTo(urlDevice2), 0); // identical device - assertEquals(mUrlDevice1.compareTo(urlDevice3), 0); // same info, but uses a RankedDevice - assertEquals(urlDevice3.compareTo(mUrlDevice1), 0); // reverse comparison - } - - @Test - public void compareDeviceWithDifferentRankReturnsZero() { - UrlDevice urlDevice2 = new RankedDevice(ID1, URL1, RANK2); // different rank - assertEquals(mUrlDevice1.compareTo(urlDevice2), 0); // compareTo should not consider rank - assertEquals(urlDevice2.compareTo(mUrlDevice1), 0); - } - - @Test - public void compareDeviceToUnalikeDeviceReturnsNonZero() { - UrlDevice urlDevice2 = new UrlDevice(ID2, URL1); // different device ID - UrlDevice urlDevice3 = new UrlDevice(ID1, URL2); // different URL - assertTrue(mUrlDevice1.compareTo(urlDevice2) < 0); // "id1" < "id2" - assertTrue(urlDevice2.compareTo(mUrlDevice1) > 0); - assertTrue(mUrlDevice1.compareTo(urlDevice3) < 0); // "example.com" < "physical-web.org" - assertTrue(urlDevice3.compareTo(mUrlDevice1) > 0); - } -} diff --git a/java/libs/src/test/java/org/physical_web/collection/UrlGroupTest.java b/java/libs/src/test/java/org/physical_web/collection/UrlGroupTest.java deleted file mode 100644 index 41acc3d2..00000000 --- a/java/libs/src/test/java/org/physical_web/collection/UrlGroupTest.java +++ /dev/null @@ -1,163 +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.collection; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; - -/** - * UrlGroup unit test class. - */ -public class UrlGroupTest { - private static final String ID1 = "id1"; - private static final String ID2 = "id2"; - private static final String URL1 = "http://physical-web.org/#a"; - private static final String URL2 = "http://physical-web.org/#b"; - private static final String GROUPID1 = "group1"; - private static final String GROUPID2 = "group2"; - private static final double RANK1 = 0.5d; - private static final double RANK2 = 0.9d; - - private PwPair mPwPair1; - private PwPair mPwPair2; - - @Before - public void setUp() { - mPwPair1 = RankedDevice.createRankedPair(ID1, URL1, GROUPID1, RANK1); - mPwPair2 = RankedDevice.createRankedPair(ID2, URL2, GROUPID1, RANK2); - } - - @Test - public void constructorCreatesProperObject() { - UrlGroup urlGroup = new UrlGroup(GROUPID1); - assertEquals(urlGroup.getGroupId(), GROUPID1); - } - - @Test - public void addPairAndGetTopPairWorks() { - UrlGroup urlGroup = new UrlGroup(GROUPID1); - urlGroup.addPair(mPwPair1); - - PwPair pwPair = urlGroup.getTopPair(); - assertEquals(pwPair.getUrlDevice().getId(), ID1); - assertEquals(pwPair.getUrlDevice().getUrl(), URL1); - assertEquals(pwPair.getPwsResult().getRequestUrl(), URL1); - assertEquals(pwPair.getPwsResult().getSiteUrl(), URL1); - assertEquals(pwPair.getPwsResult().getGroupId(), GROUPID1); - } - - @Test - public void addPairTwiceAndGetTopPairWorks() { - UrlGroup urlGroup = new UrlGroup(GROUPID1); - urlGroup.addPair(mPwPair1); - urlGroup.addPair(mPwPair2); // higher rank - - PwPair pwPair = urlGroup.getTopPair(); - assertEquals(pwPair.getUrlDevice().getId(), ID2); - } - - @Test - public void groupIsEqualToItself() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - assertEquals(urlGroup1, urlGroup1); - } - - @Test - public void alikeEmptyGroupsAreEqual() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); // same groupid - assertEquals(urlGroup1, urlGroup2); - } - - @Test - public void unalikeEmptyGroupsAreNotEqual() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID2); // different groupid - assertNotEquals(urlGroup1, urlGroup2); - } - - @Test - public void alikeGroupsAreEqual() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); - urlGroup1.addPair(mPwPair1); - urlGroup1.addPair(mPwPair2); - urlGroup2.addPair(mPwPair2); // add the pairs in a different order - urlGroup2.addPair(mPwPair1); - assertEquals(urlGroup1, urlGroup2); - } - - @Test - public void unalikeGroupsAreNotEqual() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); // same groupid - UrlGroup urlGroup3 = new UrlGroup(GROUPID2); // different groupid - PwPair pwPair1 = RankedDevice.createRankedPair(ID1, URL1, GROUPID1, RANK1); - PwPair pwPair2 = RankedDevice.createRankedPair(ID2, URL2, GROUPID1, RANK1); - PwPair pwPair3 = RankedDevice.createRankedPair(ID1, URL1, GROUPID2, RANK1); - urlGroup1.addPair(pwPair1); - urlGroup2.addPair(pwPair2); - urlGroup3.addPair(pwPair3); - assertNotEquals(urlGroup1, urlGroup2); // same groupid, different URLs - assertNotEquals(urlGroup1, urlGroup3); // different groupid, same URLs - urlGroup1.addPair(pwPair2); - assertNotEquals(urlGroup1, urlGroup2); // urlGroup1 has two items, urlGroup2 has only one - } - - @Test - public void compareGroupToItselfReturnsZero() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - assertEquals(urlGroup1.compareTo(urlGroup1), 0); // compare empty group to itself - urlGroup1.addPair(mPwPair1); - assertEquals(urlGroup1.compareTo(urlGroup1), 0); // compare non-empty group to itself - } - - @Test - public void compareGroupToAlikeGroupReturnsZero() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); - assertEquals(urlGroup1.compareTo(urlGroup2), 0); // compare identical empty groups - urlGroup1.addPair(mPwPair1); // add pair 1 to both - urlGroup2.addPair(mPwPair1); - assertEquals(urlGroup1.compareTo(urlGroup2), 0); // compare identical groups with one pair - } - - @Test - public void compareGroupToAlikeGroupWithDifferentOrderReturnsZero() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); - urlGroup1.addPair(mPwPair1); // add pair1, then pair2 - urlGroup1.addPair(mPwPair2); - urlGroup2.addPair(mPwPair2); // add pair2, then pair1 - urlGroup2.addPair(mPwPair1); - assertEquals(urlGroup1.compareTo(urlGroup2), 0); - } - - @Test - public void compareGroupToUnalikeGroupReturnsNonZero() { - UrlGroup urlGroup1 = new UrlGroup(GROUPID1); - UrlGroup urlGroup2 = new UrlGroup(GROUPID1); - UrlGroup urlGroup3 = new UrlGroup(GROUPID2); - assertTrue(urlGroup1.compareTo(urlGroup3) < 0); // "group1" < "group2" - PwPair pwPair3 = RankedDevice.createRankedPair(ID1, URL1, GROUPID1, RANK2); - urlGroup1.addPair(mPwPair1); - urlGroup2.addPair(pwPair3); - assertTrue(urlGroup1.compareTo(urlGroup2) < 0); // 0.5 < 0.9 - assertTrue(urlGroup2.compareTo(urlGroup1) > 0); - } -} diff --git a/mobile/android/getting-started.html b/mobile/android/getting-started.html new file mode 100644 index 00000000..5febf5a3 --- /dev/null +++ b/mobile/android/getting-started.html @@ -0,0 +1,53 @@ + + + + + + + +
+ +

The Physical Web

+

Walk up and use anything.

+
+ +
+

The Physical Web is an open source project to merge the web and physical world.

+

This app scans for beacons while the screen is on and will gather information anonymously from websites automatically through a server.

+

Since the project is at a very early stage, you're not likely to see much unless you set up some beacons yourself. If you have a test beacon, this walkthrough will get you started.

+

Please go to the project page and report any issues you find.

+ + +
+ diff --git a/mobile/images/logo-blue.png b/mobile/images/logo-blue.png new file mode 100644 index 00000000..24aa547b Binary files /dev/null and b/mobile/images/logo-blue.png differ diff --git a/mobile/images/logo-white.png b/mobile/images/logo-white.png new file mode 100644 index 00000000..07478190 Binary files /dev/null and b/mobile/images/logo-white.png differ diff --git a/mobile/ios/getting-started.html b/mobile/ios/getting-started.html new file mode 100644 index 00000000..de7a0396 --- /dev/null +++ b/mobile/ios/getting-started.html @@ -0,0 +1,53 @@ + + + + + + + +
+ +

The Physical Web

+

Walk up and use anything.

+
+ +
+

The Physical Web is an open source project to merge the web and physical world.

+

This app scans for beacons while the screen is on and will gather information anonymously from websites automatically through a server.

+

Since the project is at a very early stage, you're not likely to see much unless you set up some beacons yourself. If you have a test beacon, this walkthrough will get you started.

+

Please go to the project page and report any issues you find.

+ + +
+ diff --git a/nodejs/README.md b/nodejs/README.md deleted file mode 100644 index 9872a2c6..00000000 --- a/nodejs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -## node-eddystone-beacon-scanner - -There is an excellent node.js scanner module written by [@sandeepmistry](https://github.com/sandeepmistry). If you'd like to build a RasperryPi/laptop client to scan for beacons, please use this. - -[node-eddystone-beacon-scanner](https://github.com/sandeepmistry/node-eddystone-beacon-scanner) - -## physical-web-scan - -If you don't want to build your own client, you can use this for Mac OSX. It fetches metadata from the [Physical Web Service](../web-service) and displays found beacons in both the terminal and notification center. - -[physical-web-scan](https://github.com/dermike/physical-web-scan) diff --git a/resources.html b/resources.html new file mode 100644 index 00000000..84cfcbbd --- /dev/null +++ b/resources.html @@ -0,0 +1,108 @@ + + + + + + + + + The Physical Web + + + + + + + + + + + + + + + + + + + +
+
+
+

Resources

+
+
+

Getting Started

+

Learn how to configure, deploy, and manage beacons for a Physical Web deployment.

+ Read More +
+
+
+
+

Try Physical Web

+

Learn how to set up the Physical Web on iOS and Android.

+ Read More +
+
+
+
+

URL Validator

+

Check how your Physical Web URL will show up on Android and iOS.

+ Verify URL +
+
+
+
+

Configure beacons

+

Configure beacons straight from the web using the experimental Web Bluetooth API (supported browsers and platforms here).

+ Configure +
+
+
+
+

Github

+

Dive into open source implementations of the Physical Web on Github.

+ Visit +
+
+
+
+

How it works

+

Watch an a more in-depth overview of how the Physical Web works.

+ Watch +
+
+ +
+
+
+ + + + diff --git a/travis/build.sh b/travis/build.sh deleted file mode 100755 index 023f46ff..00000000 --- a/travis/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# Test the android project -( - cd android/PhysicalWeb/ - # This will run our linters etc. - # NOTE: check depends on assembleDebug - ./gradlew check -) - -# Test the Physical Web java libraries -( - cd java/libs - ./gradlew check - ./gradlew test -) diff --git a/try-physical-web.html b/try-physical-web.html new file mode 100644 index 00000000..7bd65ea4 --- /dev/null +++ b/try-physical-web.html @@ -0,0 +1,170 @@ + + + + + + + + + The Physical Web + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+

+Setting up Physical Web in Chrome

+

The Physical Web enables you to discover web pages associated with everyday objects and locations. It is powered by bluetooth low energy (BLE) beacons that broadcast URLs using the Eddystone format. Various mobile browsers are working to display these URLs. This document explains how to use the Physical Web on Chrome and Nearby Notifications. + +

If you experience any issues with this setup process, reach out to physical-web-discuss@googlegroups.com.

+ +

+Nearby Notifications

+

Nearby Notifications is an Android feature available on devices running Android KitKat (4.4) and above. When you are near a bluetooth beacon (and have bluetooth enabled), you will receive a notification for the Physical Web.

+

When you are near a beacon, you will see the following:

+
    +
  1. + First, check that you have an active data connection as well as Bluetooth and Location turned on. The notification shade provides an easy way to check that these requirements are met. + Check active data connection, bluetooth and location turned on +
  2. +
  3. + Swipe down on the notification shade when you first encounter a beacon. You will receive a notification alerting you about nearby web pages. + Initial nearby notification +
  4. +
  5. + Tap on the notification to opt into future Nearby notifications. Opting in will take you to a list of nearby URLs. +
    + Opt in screen + List of URLs +
    +
  6. +
  7. + When you are near a beacon in the future, you will see a notification informing you of nearby URLs. + Notification +
  8. +
  9. + To enable or disable Nearby Notifications in the future, visit Settings > Google > Nearby Notifications. + Nearby Notifications settings +
  10. +
+ +

+Chrome on Android

+

The Physical Web is available starting Chrome version 49 and on devices running Android KitKat (4.4) and above. When you are near a beacon for the first time (and if you have Bluetooth enabled) you will receive a notification describing the Physical Web.

+

If you’d like to explicitly turn on Physical Web, try the following:

+
    +
  1. + Check that you have an active data connection as well as Bluetooth and Location turned on, as outlined above in the Nearby Notifications section. +
  2. +
  3. + Enable the Physical Web privacy option from within Chrome in Privacy settings: +
    + Enable the Physical Web privacy option from within Chrome in Privacy settings + Enable the Physical Web privacy option from within Chrome in Privacy settings +
    +
    + Enable the Physical Web privacy option from within Chrome in Privacy settings + Enable the Physical Web privacy option from within Chrome in Privacy settings +
    + Enable the Physical Web privacy option from within Chrome in Privacy settings + +

    (Marshmallow ONLY) On Marshmallow (Android 6.0) devices, the Physical Web requires that Chrome is granted the Location runtime permission. If you have not already granted this permission, enabling the Physical Web privacy option will automatically prompt you to grant it. This step is not necessary on pre-Marshmallow versions of Android.

    + On Marshmallow (Android 6.0) devices, the Physical Web requires that Chrome is granted the Location runtime permission. +
  4. +
  5. + To see URLs when you are nearby a beacon in the future, swipe down on your notification shade to see a low priority notification informing you of nearby Physical Web URLs. + swipe down on your notification shade to see a low priority notification +
  6. +
  7. + Tap on notification. You will see a list of nearby URLs. +
  8. +
+ +

+Chrome on iOS

+

As of Oct 20, 2017 Google has removed physical web from the Chrome browser on iOS

+ +

+Potential issues

+
    +
  1. +

    I don’t see any URLs around after successfully going through these steps.

    +

    It’s possible there aren’t any BLE beacons nearby. If you have a compatible Nexus device (Nexus 6, Nexus 9, Galaxy Note 4, Galaxy S6, Galaxy Tab S), you can create a virtual beacon using the Beacon Toy app. Otherwise, consider setting up your own beacon.

    + +

    Additionally, nearby broadcasted URLs must be public. The URL will be accessed by a Google service to resolve the URL and retrieve page metadata. If the page is not accessible from the Internet (for instance, it requires authentication or is only accessible from an internal network) then it will not appear in the list of results.

    + +

    Finally, all URLs must resolve to an HTTPS URL. For our users’ security, we require all Physical Web pages be served over HTTPS. You may use a URL shortener that generates HTTP URLs as long as the shortened URL resolves to an HTTPS URL.

    +
  2. +
+ +
+
+
+
+
+ + + + + + diff --git a/validator.html b/validator.html new file mode 100644 index 00000000..f232f7c5 --- /dev/null +++ b/validator.html @@ -0,0 +1,55 @@ + + + + + + + + + The Physical Web + + + + + + + + + + + + + + + + + + + +
+

Validator Tool

+
+ + + + + diff --git a/web-service/.gitignore b/web-service/.gitignore deleted file mode 100644 index 67bf8852..00000000 --- a/web-service/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Common Nonsense -**/*.pyc -**/.DS_Store - -# Do not commit these files -/API_KEYS.json -*.SECRET.* diff --git a/web-service/LICENSE b/web-service/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/web-service/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/web-service/README.md b/web-service/README.md deleted file mode 100644 index 6bfa36b0..00000000 --- a/web-service/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Physical Web Service - -This is an App Engine project which implements a sample Physical Web metadata -service. The use of a Physical Web Service is not necessary, but remarkably -useful. It helps clients resolve URLs in a safer and more efficient manner. -Read below for more details. - -This particular implementation is made to work with the sample Physical Web -applications in this repo. - - -## Why do we use a Physical Web Service? - -[Eddystone-URL](https://github.com/google/eddystone/tree/master/eddystone-url) beacons have an open but very specific protocol which must be -followed. Anyone is free to do so. This is the only requirement for -participation and interaction with the Physical Web. - -Any app/device can read these Eddystone-URL packets and do with them whatever they -wish. We have provided some example apps (and published them to app stores) to -make it easier for you to try out the Physical Web. - -Our app of course wants to provide a beautiful user experience, with rich -information about the URLs it finds. Even more so, it wants to protect the -user and his/her privacy from the many potential misuses of the physical web. -We hope every other app will do so, also. - -One really nice property of the Physical Web is that Eddystone-URL beacons cannot -physically detect when clients scan them and so cannot track passers-by. -However, the web servers backing these URLs can track all requests to them -(as is usual on the web). So our app must be extremely careful with how it -uses the URLs it finds. - -Yet, showing raw URLs alone is not descriptive enough for most users, and is -basically useless to anyone when those URLs are obfuscated strings coming from -a URL shortener (which is common due to the URL length limit). - -We would like to grab a nice Title / Icon from the actual content of the page. -But that requires making a request for the page, which may potentially be -abused by malicious parties. This is the crux of the issue, which we have -solved by going through a trusted intermediary. We call this a Physical Web -Service. - - -## What does a Physical Web Service do? - -At the very simplest, it fetches, parses, and presents the content of Eddystone-URL packets on behalf of a client, but without using the client's identity in any way. -It's a middleman added for safety and efficiency. - -Unlike an Eddystone-URL beacon, a Physical Web Service is not a core part of the Physical Web, is not mandatory, and does not have a specific protocol that must be -followed (though perhaps an ad-hoc format will arise one day). It is an -auxiliary solution to solve a fundamental problem for Physical Web client -software (see above). - -Over time, we've found this to be an elegant way to solve a bunch of other -problems for us as well by making the Physical Web: -* Faster, because we offload the heavy task of content parsing to the server. -* Cheaper, because we offload the network request costs to the server. -* Safer, because we can introduce safe-search filtering of inappropriate - content. -* Better, because we can rank results based on various metrics, much as a - search engine does. - - -## How to run your own Physical Web Service - -You can run your own Service by taking a look at the -[source code](https://github.com/google/physical-web/tree/master/web-service) -and following typical -[App Engine deployment documents](https://cloud.google.com/appengine/docs/python/gettingstartedpython27/uploading), -or try it locally first by using a -[development server](https://cloud.google.com/appengine/docs/python/tools/devserver). -Eventually, you will want to update the `app.yaml` to create a new application -ID, and take a look at `config.SAMPLE.json`. - -If you are building one of our sample apps and would like to use your own -Physical Web Service, you can change the endpoint by modifying: - -* Android: Update `PROD_URL` and `DEV_URL` in - [`PwsClient.java`](https://github.com/google/physical-web/blob/7c7b5c00f2e6eb08c9730be36a98954334a2e8c6/android/PhysicalWeb/app/src/main/java/org/physical_web/physicalweb/MetadataResolver.java#L48) - and `proxy_go_link_base_url` in - [`strings.xml`](https://github.com/google/physical-web/blob/7c7b5c00f2e6eb08c9730be36a98954334a2e8c6/android/PhysicalWeb/app/src/main/res/values/strings.xml#L42) -* iOS: Update `PHYSICALWEB_SERVER_HOSTNAME` in - [`PWMetadataRequest.m`](https://github.com/google/physical-web/blob/7c7b5c00f2e6eb08c9730be36a98954334a2e8c6/ios/PhyWeb/Backend/PWMetadataRequest.m#L22) - - -## How to run the Physical Web Service tests - -1. Make sure you have created config.SECRET.json from the config.SAMPLE.json - file. -2. Install nose `pip install nose` -3. Run ./tests.py -h to see help -4. Run ./tests.py to test the development server diff --git a/web-service/app.yaml b/web-service/app.yaml deleted file mode 100644 index 62da6b57..00000000 --- a/web-service/app.yaml +++ /dev/null @@ -1,53 +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. -# - -application: url-caster -version: 1 -runtime: python27 -api_version: 1 -threadsafe: yes - -handlers: -- url: /favicon\.ico - static_files: favicon.ico - upload: favicon\.ico - -- url: /webui - static_files: static/index.html - upload: static/index\.html - -- url: /webui.raw - static_files: static/raw.html - upload: static/raw\.html - -- url: /shorten-url - script: shortener.app - -- url: /experimental/.* - script: experimental.app - -- url: .* - script: handlers.app - -libraries: -- name: webapp2 - version: "2.5.2" - -- name: lxml - version: "2.3.5" - -builtins: -- appstats: on diff --git a/web-service/appengine_config.py b/web-service/appengine_config.py deleted file mode 100644 index c909f0c9..00000000 --- a/web-service/appengine_config.py +++ /dev/null @@ -1,4 +0,0 @@ -def webapp_add_wsgi_middleware(app): - from google.appengine.ext.appstats import recording - app = recording.appstats_wsgi_middleware(app) - return app diff --git a/web-service/config.SAMPLE.json b/web-service/config.SAMPLE.json deleted file mode 100644 index dd78c711..00000000 --- a/web-service/config.SAMPLE.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "oauth_keys": { - "goo.gl": "Insert OAuth key here, by following these docs: https://developers.google.com/url-shortener/v1/getting_started" - } -} diff --git a/web-service/experimental.py b/web-service/experimental.py deleted file mode 100755 index b8ac9af2..00000000 --- a/web-service/experimental.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Google Inc. -# -# 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. -# -import helpers -import json -import logging -import models -import webapp2 - -################################################################################ - -class GooglRedirect(webapp2.RequestHandler): - def get(self, path): - return self._redirect(path); - - def head(self, path): - return self._redirect(path); - - def _redirect(self, path): - try: - distance = float(self.request.headers['X-PhysicalWeb-Distance']) - except: - distance = None - - logging.info('GoogleRedirect with distance:{0}'.format(distance)) - - if distance > 2: - self.response.set_status(204) - return - - self.redirect('http://goo.gl/{0}'.format(path)) - -################################################################################ - -app = webapp2.WSGIApplication([ - ('/experimental/googl/(.*)', GooglRedirect), -], debug=True) - -if not helpers.ENABLE_EXPERIMENTAL: - app = webapp2.WSGIApplication([], debug=True) diff --git a/web-service/favicon.ico b/web-service/favicon.ico deleted file mode 100644 index 8a832ebe..00000000 Binary files a/web-service/favicon.ico and /dev/null differ diff --git a/web-service/handlers.py b/web-service/handlers.py deleted file mode 100755 index 9f86f4d3..00000000 --- a/web-service/handlers.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Google Inc. -# -# 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. -# -from urllib import unquote_plus -import helpers -import json -import logging -import models -import webapp2 - -################################################################################ - -class Index(webapp2.RequestHandler): - def get(self): - self.response.out.write('') - - def head(self): - pass - -################################################################################ - -class GoUrl(webapp2.RequestHandler): - def get(self): - return self._redirect() - - def head(self): - return self._redirect() - - def _redirect(self): - url = self.request.get('url') - url = url.encode('ascii', 'ignore') - self.redirect(url) - -################################################################################ - -class RefreshUrl(webapp2.RequestHandler): - def post(self): - url = self.request.get('url') - helpers.RefreshUrl(url) - -################################################################################ - -class FaviconUrl(webapp2.RequestHandler): - def get(self): - url = unquote_plus(self.request.get('url')) - response = helpers.FaviconUrl(url) - if response: - self.response.headers['Content-Type'] = response.headers['Content-Type'] - self.response.write(response.content) - else: - self.error('404') - -################################################################################ - -class ResolveScan(webapp2.RequestHandler): - def post(self): - input_data = self.request.body - - try: - input_object = json.loads(input_data) # TODO: Data is not sanitised. - objects = input_object.get('objects', []) - except: - objects = [] - - output = helpers.BuildResponse(objects) - - self.response.headers['Content-Type'] = 'application/json' - json_data = json.dumps(output); - self.response.write(json_data) - -################################################################################ - -class DemoMetadata(webapp2.RequestHandler): - def get(self): - objects = [ - {'url': 'http://www.caltrain.com/schedules/realtime/stations/mountainviewstation-mobile.html'}, - {'url': 'http://benfry.com/distellamap/'}, - {'url': 'http://en.wikipedia.org/wiki/Le_D%C3%A9jeuner_sur_l%E2%80%99herbe'}, - {'url': 'http://sfmoma.org'} - ] - output = helpers.BuildResponse(objects) - - self.response.headers['Content-Type'] = 'application/json' - json_data = json.dumps(output); - self.response.write(json_data) - - def head(self): - pass - -################################################################################ - -app = webapp2.WSGIApplication([ - ('/', Index), - ('/resolve-scan', ResolveScan), - ('/refresh-url', RefreshUrl), - ('/favicon', FaviconUrl), - ('/go', GoUrl), - ('/demo', DemoMetadata) -], debug=True) diff --git a/web-service/helpers.py b/web-service/helpers.py deleted file mode 100755 index 68311da2..00000000 --- a/web-service/helpers.py +++ /dev/null @@ -1,471 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Google Inc. -# -# 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. - -try: - from google.appengine.api import taskqueue, urlfetch, app_identity - import models -except Exception as e: - if __name__ != '__main__': - raise e - else: - print "Warning: import exception '{0}'".format(e) - -from urllib import quote_plus -from urlparse import urljoin, urlparse, urlsplit, urlunsplit -import cgi -import datetime -import json -import logging -import lxml.etree - -################################################################################ - -ENABLE_EXPERIMENTAL = app_identity.get_application_id().endswith('-dev') -PHYSICAL_WEB_USER_AGENT = 'Mozilla/5.0' # TODO: Find a more descriptive string. -BASE_URL = 'https://' + app_identity.get_application_id() + '.appspot.com' - -################################################################################ - -def BuildResponse(objects): - metadata_output = [] - unresolved_output = [] - - # Resolve the devices - for obj in objects: - url = obj.get('url', None) - force_update = obj.get('force', False) - rssi = obj.get('rssi', None) - txpower = obj.get('txpower', None) - distance = ComputeDistance(rssi, txpower) - - def append_invalid(): - #unresolved_output.append({ - # 'id': url - #}) - pass - - if url is None: - continue - - parsed_url = urlparse(url) - if parsed_url.scheme != 'http' and parsed_url.scheme != 'https': - append_invalid() - continue - - try: - siteInfo = GetSiteInfoForUrl(url, distance, force_update) - except FailedFetchException: - append_invalid() - continue - - if siteInfo is None: - # It's a valid url, which we didn't fail to fetch, so it must be `No Content` - continue - - scheme, netloc, path, query, fragment = urlsplit(siteInfo.url) - if fragment == '': - fragment = parsed_url.fragment - finalUrl = urlunsplit((scheme, netloc, path, query, fragment)) - - device_data = {} - device_data['id'] = url - # TODO: change url to the original url (perhaps minus our goo.gl shortened values) - device_data['url'] = finalUrl - # TODO: change displayUrl to the "most applicable" url (resolve shorteners, but perhaps not all redirects) - device_data['displayUrl'] = finalUrl - if siteInfo.title is not None: - device_data['title'] = siteInfo.title - if siteInfo.description is not None: - device_data['description'] = siteInfo.description - if siteInfo.favicon_url is not None: - device_data['icon'] = urljoin(BASE_URL, '/favicon?url=' + quote_plus(siteInfo.favicon_url)) - if siteInfo.jsonlds is not None: - device_data['json-ld'] = json.loads(siteInfo.jsonlds) - device_data['distance'] = distance - try: - device_data['groupid'] = ComputeGroupId(siteInfo.url, siteInfo.title, siteInfo.description) - except Exception as e: - logging.error('ComputeGroupId url:{0}, title:{1}, description:{2}'.format(siteInfo.url, siteInfo.title, siteInfo.description)) - - metadata_output.append(device_data) - - - metadata_output = map(ReplaceDistanceWithRank, RankedResponse(metadata_output)) - - ret = { - "metadata": metadata_output, - } - - if unresolved_output: - ret["unresolved"] = unresolved_output - - return ret - -################################################################################ - -def ComputeDistance(rssi, txpower): - try: - rssi = float(rssi) - txpower = float(txpower) - if rssi in [127, 128]: # Known invalid rssi values - return None - path_loss = txpower - rssi - distance = pow(10.0, (path_loss - 41) / 20) - return distance - except: - return None - -def RankedResponse(metadata_output): - def SortByDistanceCmp(a, b): - return cmp(a['distance'], b['distance']) - - metadata_output.sort(SortByDistanceCmp) - return metadata_output - -def ReplaceDistanceWithRank(device_data): - distance = device_data['distance'] - distance = distance if distance is not None else 1000 - device_data['rank'] = distance - device_data.pop('distance', None) - return device_data - -################################################################################ - -def ComputeGroupId(url, title, description): - import hashlib - domain = urlparse(url).netloc - seed = domain + '\0' + title - groupid = hashlib.sha1(seed.encode('utf-8')).hexdigest()[:16] - return groupid - -################################################################################ - -# This is used to recursively look up in cache after each redirection. -# We don't cache the redirection itself, but we always want to cache the final destination. -def GetSiteInfoForUrl(url, distance=None, force_update=False): - logging.info('GetSiteInfoForUrl url:{0}, distance:{1}'.format(url, distance)) - - siteInfo = None - - if force_update: - siteInfo = FetchAndStoreUrl(siteInfo, url, distance, force_update) - else: - siteInfo = models.SiteInformation.get_by_id(url) - - if siteInfo is None: - siteInfo = FetchAndStoreUrl(siteInfo, url, distance, force_update) - else: - # If the cache is older than 5 minutes, queue a refresh - updated_ago = datetime.datetime.now() - siteInfo.updated_on - if updated_ago > datetime.timedelta(minutes=5): - logging.info('Queue RefreshUrl for url: {0}, which was updated {1} ago'.format(url, updated_ago)) - # Add request to queue. - taskqueue.add(url='/refresh-url', params={'url': url}) - - return siteInfo - -################################################################################ - -class FailedFetchException(Exception): - pass - -def FetchAndStoreUrl(siteInfo, url, distance=None, force_update=False): - # Index the page - try: - headers = {'User-Agent': PHYSICAL_WEB_USER_AGENT} - if ENABLE_EXPERIMENTAL and distance is not None: - headers['X-PhysicalWeb-Distance'] = distance - - result = urlfetch.fetch(url, - follow_redirects=False, - validate_certificate=True, - headers=headers) - except: - logging.info('FetchAndStoreUrl FailedFetch url:{0}'.format(url)) - raise FailedFetchException() - - logging.info('FetchAndStoreUrl url:{0}, status_code:{1}'.format(url, result.status_code)) - if result.status_code == 200 and result.content: # OK - encoding = GetContentEncoding(result.content) - assert result.final_url is None - # TODO: Use the cache-content headers for storeUrl! - return StoreUrl(siteInfo, url, result.content, encoding) - elif result.status_code == 204: # No Content - return None - elif result.status_code in [301, 302, 303, 307, 308]: # Moved Permanently, Found, See Other, Temporary Redirect, Permanent Redirect - final_url = urljoin(url, result.headers['location']) - - scheme, netloc, path, query, fragment = urlsplit(final_url) - if fragment == '': - fragment = urlparse(url).fragment - final_url = urlunsplit((scheme, netloc, path, query, fragment)) - - logging.info('FetchAndStoreUrl url:{0}, redirects_to:{1}'.format(url, final_url)) - if siteInfo is not None: - logging.info('Removing Stale Cache for url:{0}'.format(url)) - siteInfo.key.delete() - # TODO: Most redirects should not be cached, but we should still check! - return GetSiteInfoForUrl(final_url, distance, force_update) - elif 500 <= result.status_code <= 599: - return None - else: - raise FailedFetchException() - -################################################################################ - -def GetContentEncoding(content): - try: - # Don't assume server return proper charset and always try UTF-8 first. - u_value = unicode(content, 'utf-8') - return 'utf-8' - except UnicodeDecodeError: - pass - - encoding = None - parser = lxml.etree.HTMLParser(encoding='iso-8859-1') - htmltree = lxml.etree.fromstring(content, parser) - value = htmltree.xpath("//head//meta[@http-equiv='Content-Type']/attribute::content") - if encoding is None: - if (len(value) > 0): - content_type = value[0] - _, params = cgi.parse_header(content_type) - if 'charset' in params: - encoding = params['charset'] - - if encoding is None: - value = htmltree.xpath('//head//meta/attribute::charset') - if (len(value) > 0): - encoding = value[0] - - if encoding is None: - encoding = 'iso-8859-1' - u_value = unicode(content, 'iso-8859-1') - - return encoding - -################################################################################ - -def FlattenString(input): - input = input.strip() - input = input.replace('\r', ' '); - input = input.replace('\n', ' '); - input = input.replace('\t', ' '); - input = input.replace('\v', ' '); - input = input.replace('\f', ' '); - while ' ' in input: - input = input.replace(' ', ' '); - return input - -################################################################################ - -def StoreUrl(siteInfo, url, content, encoding): - title = None - description = None - icon = None - - # parse the content - parser = lxml.etree.HTMLParser(encoding=encoding) - htmltree = lxml.etree.fromstring(content, parser) - - # Try to find web manifest . - value = htmltree.xpath("//link[@rel='manifest']/attribute::href") - if (len(value) > 0): - # Fetch web manifest. - manifestUrl = value[0] - if "://" not in manifestUrl: - manifestUrl = urljoin(url, manifestUrl) - try: - result = urlfetch.fetch(manifestUrl) - if result.status_code == 200: - manifestData = json.loads(result.content) - if 'short_name' in manifestData: - title = manifestData['short_name'] - else: - title = manifestData['name'] - except: - pass - - # Try to use .... - if title is None: - value = htmltree.xpath('//head//title/text()'); - if (len(value) > 0): - title = value[0] - if title is None: - value = htmltree.xpath("//head//meta[@property='og:title']/attribute::content"); - if (len(value) > 0): - title = value[0] - if title is not None: - title = FlattenString(title) - - # Try to use . - value = htmltree.xpath("//head//meta[@name='description']/attribute::content") - if (len(value) > 0): - description = value[0] - if description is not None and len(description) == 0: - description = None - if description == title: - description = None - - # Try to use . - if description is None: - value = htmltree.xpath("//head//meta[@property='og:description']/attribute::content") - description = ' '.join(value) - if len(description) == 0: - description = None - - # Try to use
...
. - if description is None: - value = htmltree.xpath("//body//*[@class='content']//*[not(*|self::script|self::style)]/text()") - description = ' '.join(value) - if len(description) == 0: - description = None - - # Try to use
...
. - if description is None: - value = htmltree.xpath("//body//*[@id='content']//*[not(*|self::script|self::style)]/text()") - description = ' '.join(value) - if len(description) == 0: - description = None - - # Fallback on .... - if description is None: - value = htmltree.xpath("//body//*[not(*|self::script|self::style)]/text()") - description = ' '.join(value) - if len(description) == 0: - description = None - - # Cleanup. - if description is not None: - description = FlattenString(description) - if len(description) > 500: - description = description[:500] - - # Icon - if icon is None: - value = htmltree.xpath("//head//link[@rel='shortcut icon']/attribute::href"); - if (len(value) > 0): - icon = value[0] - if icon is None: - value = htmltree.xpath("//head//link[@rel='icon']/attribute::href"); - if (len(value) > 0): - icon = value[0] - if icon is None: - value = htmltree.xpath("//head//link[@rel='apple-touch-icon-precomposed']/attribute::href"); - if (len(value) > 0): - icon = value[0] - if icon is None: - value = htmltree.xpath("//head//link[@rel='apple-touch-icon']/attribute::href"); - if (len(value) > 0): - icon = value[0] - if icon is None: - value = htmltree.xpath("//head//meta[@property='og:image']/attribute::content"); - if (len(value) > 0): - icon = value[0] - - if icon is not None: - if icon.startswith('./'): - icon = icon[2:len(icon)] - icon = urljoin(url, icon) - if icon is None: - icon = urljoin(url, '/favicon.ico') - - # json-lds - jsonlds = [] - value = htmltree.xpath("//head//script[@type='application/ld+json']/text()"); - for jsonldtext in value: - jsonldobject = None - try: - jsonldobject = json.loads(jsonldtext) # Data is not sanitised. - except (ValueError, UnicodeDecodeError): - jsonldobject = None - if jsonldobject is not None: - jsonlds.append(jsonldobject) - - if (len(jsonlds) > 0): - jsonlds_data = json.dumps(jsonlds); - else: - jsonlds_data = None - - # Add to cache - if siteInfo is None: - # Add a new value - siteInfo = models.SiteInformation.get_or_insert(url, - url = url, - title = title, - favicon_url = icon, - description = description, - jsonlds = jsonlds_data) - else: - # update the data because it already exists - siteInfo.url = url - siteInfo.title = title - siteInfo.favicon_url = icon - siteInfo.description = description - siteInfo.jsonlds = jsonlds_data - siteInfo.put() - - return siteInfo - -################################################################################ - -def FaviconUrl(url): - # Fetch only favicons for sites we've already added to our database. - if models.SiteInformation.query(models.SiteInformation.favicon_url==url).count(limit=1): - try: - headers = {'User-Agent': PHYSICAL_WEB_USER_AGENT} - return urlfetch.fetch(url, headers=headers) - except: - return None - return None - -################################################################################ - -def RefreshUrl(url): - siteInfo = models.SiteInformation.get_by_id(url) - - if siteInfo is not None: - # If we've done an update within the last 5 seconds, don't do another one. - # This is just to prevent abuse, accidental or otherwise - updated_ago = datetime.datetime.now() - siteInfo.updated_on - if updated_ago < datetime.timedelta(seconds=5): - logging.info('Skipping RefreshUrl for url: {0}, which was updated {1} ago'.format(url, updated_ago)) - return - - # Update the timestamp before starting the request, to make sure we do not request twice. - siteInfo.put() - - try: - FetchAndStoreUrl(siteInfo, url, force_update=True) - except FailedFetchException: - pass - - -################################################################################ - -def GetConfig(): - import os.path - if os.path.isfile('config.SECRET.json'): - fname = 'config.SECRET.json' - else: - fname = 'config.SAMPLE.json' - with open(fname) as configfile: - return json.load(configfile) - -################################################################################ - -if __name__ == '__main__': - for i in range(-22,-100,-1): - print i, ComputeDistance(i, -22) diff --git a/web-service/models.py b/web-service/models.py deleted file mode 100755 index 40ea30d0..00000000 --- a/web-service/models.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Google Inc. -# -# 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. -# -from google.appengine.ext import ndb - -class BaseModel(ndb.Model): - added_on = ndb.DateTimeProperty(auto_now_add = True) - updated_on = ndb.DateTimeProperty(auto_now = True) - -class SiteInformation(BaseModel): - url = ndb.TextProperty() - favicon_url = ndb.TextProperty(indexed = True) - title = ndb.TextProperty() - description = ndb.TextProperty() - jsonlds = ndb.TextProperty() diff --git a/web-service/shortener.py b/web-service/shortener.py deleted file mode 100644 index f952bdc8..00000000 --- a/web-service/shortener.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Google Inc. -# -# 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. - -from google.appengine.api import urlfetch -import helpers -import json -import webapp2 - -################################################################################ - -class ShortURL(webapp2.RequestHandler): - def post(self): - input_data = self.request.body - config = helpers.GetConfig() - apikey = config['oauth_keys']['goo.gl'] - url = 'https://www.googleapis.com/urlshortener/v1/url?key=' + apikey - referer = 'url-cast.physical-web.org' - result = urlfetch.fetch(url, - validate_certificate=True, - method=urlfetch.POST, - payload=input_data, - headers = { - 'Content-Type': 'application/json', - 'Referer': referer - }) - self.response.headers['Content-Type'] = 'application/json' - self.response.write(result.content) - -################################################################################ - -app = webapp2.WSGIApplication([ - ('/shorten-url', ShortURL), -], debug=True) diff --git a/web-service/static/index.html b/web-service/static/index.html deleted file mode 100644 index 41ba776b..00000000 --- a/web-service/static/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - -

/resolve-scan

-
-

- URL - Bypass cache -

-
- -
-
-

- -


-    

- - diff --git a/web-service/static/raw.html b/web-service/static/raw.html deleted file mode 100644 index 18599cf9..00000000 --- a/web-service/static/raw.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - -

/resolve-scan

-

Request Data

- -

Request Data

-

-    
-  
-
diff --git a/web-service/tests.py b/web-service/tests.py
deleted file mode 100755
index d5d29ee5..00000000
--- a/web-service/tests.py
+++ /dev/null
@@ -1,353 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2015 Google Inc.
-#
-# 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.
-#
-import argparse
-import json
-import nose
-import os
-import signal
-import subprocess
-import sys
-import unittest
-import urllib
-import urllib2
-
-LOCAL_TEST_PORT = 9002
-
-REGRESSION_TEST_URLS = [
-        'http://www.blackanddecker.fr',
-        'http://www.google.com',
-        'http://dota2.gamepedia.com/',
-        'http://www.orange.fr',
-        'http://librarian.codes',
-        'http://fredrikthalberg.com',
-        'http://harleykwyn.com',
-    ]
-
-REGRESSION_TEST_BAD_URLS = [
-        'http://google.com/asdfasdfasdfasdfa',
-        'http://www.',
-    ]
-
-class PwsTest(unittest.TestCase):
-    _HOST = None  # Set in main()
-    _ENABLE_EXPERIMENTAL = False
-
-    @property
-    def HOST(self):
-        PwsTest._HOST
-
-    @property
-    def ENABLE_EXPERIMENTAL(self):
-        PwsTest._ENABLE_EXPERIMENTAL
-
-    def request(self, params=None, payload=None):
-        """
-        Makes an http request to our endpoint
-
-        If payload is None, this performs a GET request.
-        Otherwise, the payload is json-serialized and a POST request is sent.
-
-        """
-        JSON = getattr(self, 'JSON', False)
-        url = '{}/{}'.format(self.HOST, self.PATH)
-        if params:
-            url += '?{}'.format(urllib.urlencode(params))
-        args = [url]
-        if payload is not None:
-            args.append(json.dumps(payload))
-        req = urllib2.Request(*args)
-        req.add_header("Content-Type", "application/json")
-        response = urllib2.urlopen(req)
-        data = response.read()
-        if JSON:
-            data = json.loads(data)
-            # Print so we have something nice to look at when we fail
-            print json.dumps(data, indent=2)
-        else:
-            print data
-        return response.code, data
-
-
-class TestResolveScan(PwsTest):
-    PATH = 'resolve-scan'
-    JSON = True
-
-    def call(self, values):
-        return self.request(payload=values)[1]
-
-    def test_demo_data(self):
-        result = self.call({
-            'objects': [
-                { 'url': 'http://www.caltrain.com/schedules/realtime/stations/mountainviewstation-mobile.html' },
-                { 'url': 'http://benfry.com/distellamap/' },
-                { 'url': 'http://en.wikipedia.org/wiki/Le_D%C3%A9jeuner_sur_l%E2%80%99herbe' },
-                { 'url': 'http://sfmoma.org' }
-            ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 4)
-        self.assertIn('description', result['metadata'][0])
-        self.assertIn('title', result['metadata'][0])
-        self.assertIn('url', result['metadata'][0])
-        self.assertIn('displayUrl', result['metadata'][0])
-        self.assertIn('rank', result['metadata'][0])
-        self.assertIn('id', result['metadata'][0])
-        self.assertIn('icon', result['metadata'][0])
-
-    def test_invalid_data(self):
-        result = self.call({
-            'objects': [
-                { 'url': 'http://totallybadurlthatwontwork.com/' },
-                { 'usdf': 'http://badkeys' },
-            ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 0)
-        #self.assertEqual(len(result['unresolved']), 1)
-
-
-    def test_rssi_ranking(self):
-        result = self.call({
-            'objects': [
-                {
-                    'url': 'http://www.caltrain.com/schedules/realtime/stations/mountainviewstation-mobile.html',
-                    'rssi': -75,
-                    'txpower': -22,
-                },
-                {
-                    'url': 'http://benfry.com/distellamap/',
-                    'rssi': -95,
-                    'txpower': -63,
-                },
-                {
-                    'url': 'http://en.wikipedia.org/wiki/Le_D%C3%A9jeuner_sur_l%E2%80%99herbe',
-                    'rssi': -61,
-                    'txpower': -22,
-                },
-                {
-                    'url': 'http://sfmoma.org',
-                    'rssi': -74,
-                    'txpower': -22,
-                },
-            ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 4)
-        self.assertEqual(result['metadata'][0]['id'],
-                         'http://benfry.com/distellamap/')
-        self.assertEqual(result['metadata'][1]['id'],
-                         'http://en.wikipedia.org/wiki/'
-                         'Le_D%C3%A9jeuner_sur_l%E2%80%99herbe')
-        self.assertEqual(result['metadata'][2]['id'],
-                         'http://sfmoma.org')
-        self.assertEqual(result['metadata'][3]['id'],
-                         'http://www.caltrain.com/schedules/realtime/'
-                         'stations/mountainviewstation-mobile.html')
-
-    def test_url_which_redirects(self):
-        result = self.call({
-            'objects': [
-                {
-                    'url': 'http://goo.gl/KYvLwO',
-                },
-            ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 1)
-
-        beaconResult = result['metadata'][0]
-
-        self.assertEqual(beaconResult['id'],
-                         'http://goo.gl/KYvLwO')
-        self.assertEqual(beaconResult['url'],
-                         'https://github.com/Google/physical-web')
-        self.assertEqual(beaconResult['displayUrl'],
-                         'https://github.com/Google/physical-web')
-
-    def test_redirect_with_rssi_tx_power(self):
-        if not self.ENABLE_EXPERIMENTAL:
-            return
-
-        result = self.call({
-            'objects': [
-                {
-                    'url': '{}/experimental/googl/KYvLwO'.format(self.HOST),
-                    'rssi': -41,
-                    'txpower': -22
-                },
-                {
-                    'url': '{}/experimental/googl/r8iJqW'.format(self.HOST),
-                    'rssi': -91,
-                    'txpower': -22
-                },
-            ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 1)
-        self.assertEqual(result['metadata'][0]['url'],
-                         'https://github.com/Google/physical-web')
-
-    def test_regression_urls(self):
-        result = self.call({
-            'objects': [ {'url': url} for url in REGRESSION_TEST_URLS ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), len(REGRESSION_TEST_URLS))
-
-        for beaconResult in result['metadata']:
-            self.assertIn('description', beaconResult)
-            self.assertIn('title', beaconResult)
-            self.assertIn('url', beaconResult)
-            self.assertIn('rank', beaconResult)
-            self.assertIn('id', beaconResult)
-            self.assertIn('icon', beaconResult)
-
-    def test_regression_bad_urls(self):
-        result = self.call({
-            'objects': [ {'url': url} for url in REGRESSION_TEST_BAD_URLS ]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 0)
-
-    def test_invalid_rssi(self):
-        result = self.call({
-            'objects': [{
-                'url': 'http://github.com/google/physical-web/',
-                'rssi': 127,
-                'txpower': -41
-                }]
-        })
-        self.assertIn('metadata', result)
-        self.assertEqual(len(result['metadata']), 1)
-
-        beaconResult = result['metadata'][0]
-
-        self.assertIn('description', beaconResult)
-        self.assertIn('title', beaconResult)
-        self.assertIn('url', beaconResult)
-        self.assertIn('rank', beaconResult)
-        self.assertIn('id', beaconResult)
-        self.assertIn('icon', beaconResult)
-
-        self.assertEqual(1000, beaconResult['rank'])
-
-
-class TestShortenUrl(PwsTest):
-    PATH = 'shorten-url'
-    JSON = True
-
-    def call(self, values):
-        return self.request(payload=values)[1]
-
-    def test_github_url(self):
-        result = self.call({
-            'longUrl': 'http://www.github.com/Google/physical-web'
-        })
-        self.assertIn('kind', result)
-        self.assertIn('id', result)
-        self.assertIn('longUrl', result)
-        self.assertTrue(result['id'].startswith('http://goo.gl/'))
-
-
-class RefreshUrl(PwsTest):
-    PATH = 'refresh-url'
-
-    def call(self, url):
-        params = {'url': url}
-        return self.request(params=params, payload='')[1]
-
-    def test_github_url(self):
-        result = self.call('https://github.com/google/physical-web')
-        self.assertEqual(result, '')
-
-
-class TestGo(PwsTest):
-    PATH = 'go'
-
-    def call(self, url):
-        params = {'url': url}
-        return self.request(params=params)[0]
-
-    def test_github_url(self):
-        result = self.call('https://github.com/google/physical-web')
-        self.assertEqual(result, 200)
-
-
-def main():
-    """The main routine."""
-    # Parse arguments
-    local_url = 'http://localhost:{}'.format(LOCAL_TEST_PORT)
-    parser = argparse.ArgumentParser(description='Run web-service tests')
-    parser.add_argument(
-            '-e', '--endpoint', dest='endpoint', default='auto',
-            help='Which server to test against.\n'
-                 'auto:  {} (server starts automatically)\n'
-                 'local: http://localhost:8080\n'
-                 'prod:  https://url-caster.appspot.com\n'
-                 'dev:   https://url-caster-dev.appspot.com\n'
-                 '*:     Other values interpreted literally'
-                 .format(local_url))
-    parser.add_argument('-x', '--experimental', dest='experimental', action='store_true', default=False)
-    args = parser.parse_args()
-
-    # Setup the endpoint
-    endpoint = args.endpoint
-    server = None
-    if endpoint.lower() == 'auto':
-        endpoint = local_url
-        print 'Starting local server...',
-        server = subprocess.Popen([
-            'dev_appserver.py', os.path.dirname(__file__),
-            '--port', str(LOCAL_TEST_PORT),
-            '--admin_port', str(LOCAL_TEST_PORT + 1),
-        ], bufsize=1, stderr=subprocess.PIPE, preexec_fn=os.setsid)
-        # Wait for the server to start up
-        while True:
-            line = server.stderr.readline()
-            if 'Unable to bind' in line:
-                print 'Rogue server already running.'
-                return 1
-            if 'running at: {}'.format(local_url) in line:
-                break
-        print 'done'
-    elif endpoint.lower() == 'local':
-        endpoint = 'http://localhost:8080'
-    elif endpoint.lower() == 'prod':
-        endpoint = 'https://url-caster.appspot.com'
-    elif endpoint.lower() == 'dev':
-        endpoint = 'https://url-caster-dev.appspot.com'
-    PwsTest.HOST = endpoint
-    PwsTest.ENABLE_EXPERIMENTAL = args.experimental
-
-    # Run the tests
-    try:
-        nose.runmodule()
-    finally:
-        # Teardown the endpoint
-        if server:
-            os.killpg(os.getpgid(server.pid), signal.SIGINT)
-            server.wait()
-
-    # We should never get here since nose.runmodule will call exit
-    return 0
-
-
-if __name__ == '__main__':
-    try:
-        exit(main())
-    except KeyboardInterrupt:
-        sys.stderr.write('Exiting due to KeyboardInterrupt!\n')