diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties new file mode 100644 index 0000000000..c349fe2392 --- /dev/null +++ b/.github/ci-gradle.properties @@ -0,0 +1,23 @@ +# +# Copyright 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.gradle.daemon=false +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx5120m +org.gradle.workers.max=2 + +kotlin.incremental=false +kotlin.compiler.execution.strategy=in-process \ No newline at end of file diff --git a/.github/workflows/iosched.yaml b/.github/workflows/iosched.yaml new file mode 100644 index 0000000000..41e2d65683 --- /dev/null +++ b/.github/workflows/iosched.yaml @@ -0,0 +1,80 @@ +name: iosched + +on: + push: + branches: + - main + - compose + pull_request: + branches: + - main + - compose + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + - uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches/modules-* + ~/.gradle/caches/jars-* + ~/.gradle/caches/build-cache-* + key: gradle-${{ hashFiles('checksum.txt') }} + + - name: Build project + run: ./gradlew spotlessCheck assembleDebug --stacktrace + + - name: Upload build reports + if: always() + uses: actions/upload-artifact@v2 + with: + name: build-reports + path: mobile/build/reports/ + + test: + runs-on: macOS-latest # enables hardware acceleration in the virtual machine + timeout-minutes: 30 + strategy: + matrix: + api-level: [23, 26, 29] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Run instrumentation tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + arch: x86 + disable-animations: true + script: ./gradlew mobile:cAT --stacktrace + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-reports + path: mobile/build/reports/ diff --git a/.gitignore b/.gitignore index 067f8c150f..ed6a9781b0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,16 +25,11 @@ local.properties .DS_Store # IDEA/Android Studio project files, because -# the project can be imported from settings.gradle -.idea +# the project can be imported from settings.gradle.kts *.iml - -# Old-style IDEA project files -*.ipr -*.iws - -# Local IDEA workspace -.idea/workspace.xml +.idea/* +!.idea/copyright +!.idea/codeStyles/codeStyleConfig.xml # Gradle cache .gradle @@ -42,3 +37,5 @@ local.properties # Sandbox stuff _sandbox +# Android Studio captures folder +captures/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000..2fba5e76fa --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + diff --git a/.idea/copyright/iosched.xml b/.idea/copyright/iosched.xml new file mode 100644 index 0000000000..2579fd3b8d --- /dev/null +++ b/.idea/copyright/iosched.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000000..17f54e4439 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c5227c4dcc..0000000000 --- a/.travis.yml +++ /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. - -language: android - -android: - components: - - build-tools-19.0.0 - -notifications: - email: false - -env: - matrix: - - ANDROID_SDKS=android-19,sysimg-19 ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a - -before_install: - - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI - - emulator -avd test -no-skin -no-audio -no-window & - -before_script: - - ./scripts/wait_for_emulator.sh - - adb shell input keyevent 82 & diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..3e0558e07f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# How to become a contributor and submit your own code + +## Contributor License Agreements + +We'd love to accept your sample apps and patches! Before we can take them, we +have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement +(CLA). + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual CLA] + (https://developers.google.com/open-source/cla/individual). + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA] + (https://developers.google.com/open-source/cla/corporate). + +Follow either of the two links above to access the appropriate CLA and +instructions for how to sign and return it. Once we receive it, we'll be able to +accept your pull requests. + +## Contributing A Patch + +1. Submit an issue describing your proposed change to the repo in question. +1. The repo owner will respond to your issue promptly. +1. If your proposed change is accepted, and you haven't already done so, sign a + Contributor License Agreement (see details above). +1. Fork the desired repo, develop and test your code changes. +1. Ensure that your code adheres to the existing style in the sample to which + you are contributing. Refer to the + [Google Cloud Platform Samples Style Guide] + (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the + recommended coding standards for this organization. +1. Ensure that your code has an appropriate set of unit tests which all pass. +1. Submit a pull request. + diff --git a/third_party/disklrucache/LICENSE b/LICENSE similarity index 100% rename from third_party/disklrucache/LICENSE rename to LICENSE diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d645695673..0000000000 --- a/LICENSE.txt +++ /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 index 955b98f959..a12b4b31f9 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,167 @@ -Google I/O Android App +Google I/O Android App (ARCHIVED) ====================== -Google I/O is a developer conference held each year with two days of deep -technical content featuring technical sessions and hundreds of demonstrations -from developers showcasing their technologies. - -This project is the Android app for the conference. The app supports devices -running Android 4.0+, and is optimized for phones and tablets of all shapes -and sizes. It also contains an Android Wear integration. - -

Android L Developer Preview

- -![Screenshot](art/hero.png) - -We have updated the I/O app with [material design](http://www.google.com/design/spec) and the [Android L Developer Preview](http://developer.android.com/preview/index.html)! For a quick preview of the new tactile surfaces, animated touch feedback, bold use of color, and refreshed iconography by checking out this [teaser video](https://www.youtube.com/watch?v=mCgteBXYxQc) or download the preview APK below. - -Download the I/O app APK for Android L Preview +## 2023 Update -To run this APK, you will need a device set up with the Android L Preview system image. For -more information, please refer to the [Android L Developer Preview page](http://developer.android.com/preview/index.html). +**This repository has been archived.** The Google I/O app has guided online and in-person visitors through the Google I/O conference for 10 years since 2009. It has also helped thousands of developers as an open-source sample. -

Source

+To follow Modern Android Development best practices, check out the [Now in Android](https://github.com/android/nowinandroid) repository, which replaces iosched as our real-world sample. -The source code in this repository reflects the app as of I/O 2014. +## 2021 Update -

Features

+**Due to global events, Google I/O 2020 was canceled and Google I/O 2021 is an online-only event, so +the companion app hasn't been updated since 2019. However, the `iosched` team has continued +adding several architecture improvements to its codebase. +The general look and feel of the app is unchanged, and the app +still uses the data from Google I/O 2019.** -With the app, you can: +Major improvements implemented in 2021: +* Migration from LiveData to Kotlin Flows to observe data. +* Support for large screens and other form factors. +* Migration from SharedPreferences to [Jetpack DataStore](https://developer.android.com/topic/libraries/architecture/datastore). +* (Experimental) Partial migration to Jetpack Compose +(in the [`compose`](https://github.com/google/iosched/tree/compose) branch) -- View the conference agenda and edit your personal schedule -- Sync your schedule between all of your devices and the I/O website -- View detailed session, code lab, office hours, and speaker information, - including speaker bios, photos, and Google+ profiles -- +1 sessions right from the app -- Participate in public #io14 conversations on Google+ -- Guide yourself using the vector-based conference map -- Get a reminder a few minutes before sessions in your schedule are due to - start -- View information about companies in the Developer Sandbox -- Play "I/O Live" session video streams -- Beam session details from your NFC-enabled device to another using Android Beam -- Scan attendee badges on your NFC-enabled device -- See a list of people whose badges you have scanned, and take notes about your meeting with them -- Send feedback on sessions, from your phone/tablet, or from your Android Wear device - -

How to Work with the Source

- -We hope the source code for this app is useful for you as a reference or starting point for creating your own apps. Here is some additional reading to help you better understand and reuse this code. - - * [Build instructions](doc/BUILDING.md): instructions on how to build and run the code. - * [Sync protocol and data format](doc/SYNC.md) - * [Image loading](doc/IMAGES.md) - * [Use of GCM](doc/GCM.md) - * [Customization guide](doc/CUSTOM.md) +# Description +Google I/O is a developer conference with several days of deep +technical content featuring technical sessions and hundreds of demonstrations +from developers showcasing their technologies. -

Copyright

+This project is the Android app for the conference. + +# Running the app + +The project contains a `staging` variant that replaces some modules at compile time so they +don't depend on remote services such as Firebase. This allows you to try out and test the app +without the API keys. + +# Features + +The app displays a list of conference events - sessions, office hours, app +reviews, codelabs, etc. - and allows the user to filter these events by event +types and by topics (Android, Firebase, etc.). Users can see details about +events, and they can star events that interest them. Conference attendees can +reserve events to guarantee a seat. + +Other features include a Map of the venue, informational pages to +guide attendees during the conference in Info, and time-relevant information +during the conference in Home. + +
+ Schedule screenshot +
+ +# Development Environment + +The app is written entirely in Kotlin and uses the Gradle build system. + +To build the app, use the `gradlew build` command or use "Import Project" in +Android Studio. Android Studio Arctic Fox or newer is required and may be downloaded +[here](https://developer.android.com/studio/preview). + +# Architecture + +The architecture is built around +[Android Architecture Components](https://developer.android.com/topic/libraries/architecture/) +and follows the recommendations laid out in the +[Guide to App Architecture](https://developer.android.com/jetpack/docs/guide). Logic is kept away +from Activities and Fragments and moved to +[ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel)s. +Data is observed using +[Kotlin Flows](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow) +and the [Data Binding Library](https://developer.android.com/topic/libraries/data-binding/) +binds UI components in layouts to the app's data sources. + +The Repository layer handles data operations. IOSched's data comes +from a few different sources - user data is stored in +[Cloud Firestore](https://firebase.google.com/docs/firestore/) +(either remotely or in +a local cache for offline use), user preferences and settings are stored in +DataStore, conference data is stored remotely and is fetched and stored +in memory for the app to use, etc. - and the repository modules +are responsible for handling all data operations and abstracting the data sources +from the rest of the app. + +A lightweight domain layer sits between the data layer +and the presentation layer, and handles discrete pieces of business logic off +the UI thread. See the `.\*UseCase.kt` files under `shared/domain` for +[examples](https://github.com/google/iosched/search?q=UseCase&unscoped_q=UseCase). + +The [Navigation component](https://developer.android.com/guide/navigation) is used +to implement navigation in the app, handling Fragment transactions and providing a consistent +user experience. + +[Room](https://developer.android.com/jetpack/androidx/releases/room) is used +for Full Text Search using [Fts4](https://developer.android.com/reference/androidx/room/Fts4) +to search for a session, speaker, or codelab. + +UI tests are written with [Espresso](https://developer.android.com/training/testing/espresso/) +and unit tests use Junit4 with +[Mockito](https://github.com/mockito/mockito) when necessary. + +The [Jetpack Benchmark library](https://developer.android.com/studio/profile/benchmark) +makes it easy to benchmark your code from within Android Studio. +The library handles warmup, measures your code performance, and outputs benchmarking +results to the Android Studio console. We added a few benchmark tests around +critical paths during app startup - in particular, the parsing of the bootstrap +data. This enables us to automate measuring and monitoring initial startup time. +Here is an example from a benchmark run: + +``` +Started running tests + +Connected to process 30763 on device 'google-pixel_3'. +benchmark: +benchmark: 76,076,101 ns BootstrapConferenceDataSourceBenchmark.benchmark_json_parsing +Tests ran to completion. +``` + +Dependency Injection is implemented with +[Hilt](https://developer.android.com/training/dependency-injection/hilt-android). For more details +on migrating from *dagger-android* to Hilt, read the +([migration article](https://medium.com/androiddevelopers/migrating-the-google-i-o-app-to-hilt-f3edf03affe5). + +[ViewPager2](https://developer.android.com/training/animation/screen-slide-2) offers enhanced functionality over the +original ViewPager library, such as right-to-left and vertical orientation support. +For more details on migrating from ViewPager to ViewPager2, please see this +[migration guide](https://developer.android.com/training/animation/vp2-migration). + +## Firebase + +The app makes considerable use of the following Firebase components: + +- [Cloud Firestore](https://firebase.google.com/docs/firestore/) is our source +for all user data (events starred or reserved by a user). Firestore gave us +automatic sync and also seamlessly managed offline functionality +for us. +- [Firebase Cloud Functions](https://firebase.google.com/docs/functions/) +allowed us to run backend code. The reservations feature heavily depended on Cloud +Functions working in conjuction with Firestore. +- [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/concept-options) +let us inform the app about changes to conference data on our server. +- [Remote Config](https://firebase.google.com/docs/remote-config/) helped us +manage in-app constants. + +For 2020, the implementation was migrated to the Firebase Kotlin extension (KTX) libraries to +write more idiomatic Kotlin code when calling Firebase APIs. To learn more, +read this +[Firebase blog article](https://firebase.googleblog.com/2020/03/firebase-kotlin-ga.html) +on the Firebase KTX libraries. + +## Kotlin + +The app is entirely written in Kotlin and uses Jetpack's +[Android Ktx extensions](https://developer.android.com/kotlin/ktx). + +Asynchronous tasks are handled with +[coroutines](https://developer.android.com/kotlin/coroutines). Coroutines allow for simple +and safe management of one-shot operations as well as building and consuming streams of data using +[Kotlin Flows](https://developer.android.com/kotlin/flow). + +All build scripts are written with the +[Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html). + +# Copyright Copyright 2014 Google Inc. All rights reserved. @@ -69,3 +176,4 @@ We hope the source code for this app is useful for you as a reference or startin 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/Wearable/.gitignore b/Wearable/.gitignore deleted file mode 100644 index cefcb265fc..0000000000 --- a/Wearable/.gitignore +++ /dev/null @@ -1,15 +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. - -/build diff --git a/Wearable/build.gradle b/Wearable/build.gradle deleted file mode 100644 index 50264fcfb3..0000000000 --- a/Wearable/build.gradle +++ /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. - */ - -apply plugin: 'android' - -repositories { - mavenCentral() - flatDir { - dirs 'libs' - } -} - -android { - compileSdkVersion 20 - buildToolsVersion "19.1.0" - - defaultConfig { - applicationId "com.google.samples.apps.iosched" - minSdkVersion 20 - targetSdkVersion 20 - versionCode 1 - versionName "1.0" - } - signingConfigs { - debug { - storeFile file("../android/debug.keystore") - storePassword "android" - keyAlias "androiddebugkey" - keyPassword "android" - } - release { - storeFile file("../android/debug.keystore") - storePassword "android" - keyAlias "androiddebugkey" - keyPassword "android" - } - } - - buildTypes { - debug { - debuggable true - signingConfig signingConfigs.debug - } - release { - signingConfig signingConfigs.release - } - } - - lintOptions { - abortOnError false - } -} - -dependencies { - compile 'com.android.support:support-v13:+' - compile 'com.google.android.support:wearable:+' - compile 'com.google.android.gms:play-services-wearable:+' -} - diff --git a/Wearable/proguard-rules.pro b/Wearable/proguard-rules.pro deleted file mode 100644 index 409e330f7e..0000000000 --- a/Wearable/proguard-rules.pro +++ /dev/null @@ -1,32 +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. - - -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/android-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/Wearable/src/main/AndroidManifest.xml b/Wearable/src/main/AndroidManifest.xml deleted file mode 100644 index 034b7441cf..0000000000 --- a/Wearable/src/main/AndroidManifest.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Wearable/src/main/assets/RobotoCondensed-Light.ttf b/Wearable/src/main/assets/RobotoCondensed-Light.ttf deleted file mode 100644 index 23b2ed3ecb..0000000000 Binary files a/Wearable/src/main/assets/RobotoCondensed-Light.ttf and /dev/null differ diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/FinishActivity.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/FinishActivity.java deleted file mode 100644 index ba77673829..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/FinishActivity.java +++ /dev/null @@ -1,47 +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 com.google.samples.apps.iosched.iowear; - -import android.app.Activity; -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.View; -import android.widget.TextView; - -import com.google.samples.apps.iosched.R; - -/** - * A simple activity to show that the process of collection and submission of a session feedback has - * finished. Note that there would be no indication of success or failure of the process. - */ -public class FinishActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_finish); - findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - finish(); - } - }); - Typeface typeFace = Typeface.createFromAsset(getAssets(), "RobotoCondensed-Light.ttf"); - TextView thanksText = (TextView) findViewById(R.id.thanks_text); - thanksText.setTypeface(typeFace); - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/HomeListenerService.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/HomeListenerService.java deleted file mode 100644 index cd3f703eec..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/HomeListenerService.java +++ /dev/null @@ -1,255 +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 com.google.samples.apps.iosched.iowear; - -import static com.google.samples.apps.iosched.iowear.utils.Utils.LOGD; -import static com.google.samples.apps.iosched.iowear.utils.Utils.makeLogTag; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.wearable.DataApi; -import com.google.android.gms.wearable.DataEvent; -import com.google.android.gms.wearable.DataEventBuffer; -import com.google.android.gms.wearable.DataItem; -import com.google.android.gms.wearable.DataMap; -import com.google.android.gms.wearable.DataMapItem; -import com.google.android.gms.wearable.PutDataMapRequest; -import com.google.android.gms.wearable.Wearable; -import com.google.android.gms.wearable.WearableListenerService; -import com.google.samples.apps.iosched.R; - -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; -import android.util.Log; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -/** - * A {@link com.google.android.gms.wearable.WearableListenerService} service that is invoked upon - * receiving a DataItem from the handset for session feedback notifications, or the dismissal of a - * notification. Handset application creates a Data Item that will then trigger the invocation of - * this service. That will result in creation of a wearable notification. Similarly, when a - * notification is dismissed, this service will be invoked to delete the associated data item. - */ -public class HomeListenerService extends WearableListenerService - implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { - - private static final String TAG = makeLogTag("HomeListenerService"); - public static final String KEY_SESSION_ID = "session-id"; - public static final String KEY_SESSION_NAME = "session-name"; - public static final String KEY_SPEAKER_NAME = "speaker-name"; - public static final String KEY_SESSION_ROOM = "session-room"; - public static final String KEY_NOTIFICATION_ID = "notification-id"; - public final static String ACTION_DISMISS - = "com.google.devrel.io.android.iowear.ACTION_DISMISS"; - public final static String ACTION_START_FEEDBACK - = "com.google.devrel.io.android.iowear.ACTION_FEEDBACK"; - public static final String PATH_FEEDBACK = "/iowear/feedback"; - public static final String PATH_RESPONSE = "/iowear/response"; - - public final static int NOTIFICATION_ID = 10; - private static int notificationCounter = 1; - - private GoogleApiClient mGoogleApiClient; - private boolean mConnected = false; - private final static long TIMEOUT_S = 10; // how long to wait for GoogleApi Client connection - - @Override - public void onCreate() { - super.onCreate(); - mGoogleApiClient = new GoogleApiClient.Builder(this) - .addApi(Wearable.API) - .addConnectionCallbacks(this) - .addOnConnectionFailedListener(this) - .build(); - mGoogleApiClient.connect(); - } - - public int onStartCommand(Intent intent, int flags, int startId) { - LOGD(TAG, "onStartCommand"); - if (null != intent) { - String action = intent.getAction(); - if (ACTION_DISMISS.equals(action)) { - String sessionId = intent.getStringExtra(KEY_SESSION_ID); - LOGD(TAG, "onStartCommand(): Action: ACTION_DISMISS Session: " + sessionId); - dismissPhoneNotification(sessionId); - } else if (ACTION_START_FEEDBACK.equals(action)) { - Intent feedbackIntent = new Intent(this, PagerActivity.class); - String sessionId = intent.getStringExtra(HomeListenerService.KEY_SESSION_ID); - LOGD(TAG, "Received session id from data layer: " + sessionId); - feedbackIntent.putExtra(HomeListenerService.KEY_SESSION_ID, sessionId); - feedbackIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(feedbackIntent); - NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID); - } - } - return Service.START_NOT_STICKY; - } - - /** - * Removes the Data Item that was used to create a notification on the watch. By deleting the - * data item, a {@link com.google.android.gms.wearable.WearableListenerService} on the watch - * will be notified and the notification on the watch will be removed. - *

- * Since connection to the Google API client is asynchronous, we spawn a thread nd put it to - * sleep waiting for the connection to be established before attempting to use the Google API - * client. - * - * @param sessionId The Session ID` of the notification that should be removed - */ - private void dismissPhoneNotification(final String sessionId) { - new Thread(new Runnable() { - @Override - public void run() { - if (!mConnected) { - mGoogleApiClient.blockingConnect(TIMEOUT_S, TimeUnit.SECONDS); - } - if (!mConnected) { - Log.e(TAG, "dismissPhoneNotification(): Received an interrupt exception"); - return; - } - PutDataMapRequest putDataMapRequest = PutDataMapRequest - .create(getFeedbackPath(sessionId)); - LOGD(TAG, "dismissPhoneNotification(): Dismiss wearable notification"); - if (mGoogleApiClient.isConnected()) { - LOGD(TAG, "Deleting Uri is: " + putDataMapRequest.getUri().toString()); - Wearable.DataApi.deleteDataItems(mGoogleApiClient, putDataMapRequest.getUri()) - .setResultCallback(new ResultCallback() { - @Override - public void onResult( - DataApi.DeleteDataItemsResult deleteDataItemsResult) { - if (!deleteDataItemsResult.getStatus().isSuccess()) { - LOGD(TAG, "dismissPhoneNotification(): failed to delete" - + " the data item"); - } - } - }); - } else { - Log.e(TAG, "dismissWearableNotification()): No Google API Client connection"); - } - } - }).start(); - } - - /** - * Returns the path for a feedback with the given session. - */ - private String getFeedbackPath(String sessionId) { - return PATH_FEEDBACK + "/" + sessionId; - } - - @Override - public void onDataChanged(DataEventBuffer dataEvents) { - LOGD(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName()); - - for (DataEvent event : dataEvents) { - Uri uri = event.getDataItem().getUri(); - LOGD(TAG, "onDataChanged(): Received a data item change with uri: " + uri.toString()); - if (event.getType() == DataEvent.TYPE_DELETED) { - if (uri.getPath().startsWith(PATH_FEEDBACK)) { - dismissLocalNotification(uri.getLastPathSegment()); - } - } else if (event.getType() == DataEvent.TYPE_CHANGED) { - if (uri.getPath().startsWith(PATH_FEEDBACK)) { - setupNotification(event.getDataItem()); - } - } - } - } - - /** - * Builds notification for wear based on the data in the Data Item that is passed in. - */ - private void setupNotification(DataItem dataItem) { - LOGD(TAG, "setupNotification(): DataItem=" + dataItem.getUri()); - PutDataMapRequest putDataMapRequest = PutDataMapRequest - .createFromDataMapItem(DataMapItem.fromDataItem(dataItem)); - final DataMap dataMap = putDataMapRequest.getDataMap(); - String sessionId = dataMap.getString(KEY_SESSION_ID); - String sessionRoom = dataMap.getString(KEY_SESSION_ROOM); - String sessionName = dataMap.getString(KEY_SESSION_NAME); - String speakers = dataMap.getString(KEY_SPEAKER_NAME); - - int notificationId = (int) new Date().getTime(); - Intent intent = new Intent(ACTION_DISMISS); - intent.putExtra(KEY_SESSION_ID, dataMap.getString(KEY_SESSION_ID)); - - // need to be notified if user dismisses teh notification so we can dismiss the - // corresponding notification on teh paired handset. - PendingIntent deleteIntent = PendingIntent - .getService(this, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); - - PendingIntent showCardIntent = showCardIntent(sessionId, sessionRoom, sessionName, - speakers, notificationId); - - NotificationCompat.Builder builder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(getString(R.string.rate_this_session)) - .setDeleteIntent(deleteIntent) - .setContentText(sessionName) - .extend(new NotificationCompat.WearableExtender() - .setDisplayIntent(showCardIntent)); - - NotificationManagerCompat.from(this) - .notify(sessionId, NOTIFICATION_ID, builder.build()); - } - - private PendingIntent showCardIntent(String sessionId, String sessionRoom, - String sessionName, String speakers, int notificationId) { - Intent intent = new Intent(this, NotificationActivity.class); - intent.putExtra(KEY_SESSION_ID, sessionId); - intent.putExtra(KEY_SESSION_ROOM, sessionRoom); - intent.putExtra(KEY_SESSION_NAME, sessionName); - intent.putExtra(KEY_SPEAKER_NAME, speakers); - intent.putExtra(KEY_NOTIFICATION_ID, notificationId); - return PendingIntent.getActivity(this, notificationCounter++, intent, 0); - } - - /** - * Dismisses the local notification for the given session id - */ - private void dismissLocalNotification(String sessionId) { - Log.v(TAG, "dismissLocalNotification: sessionId=" + sessionId); - - NotificationManagerCompat.from(this) - .cancel(sessionId, HomeListenerService.NOTIFICATION_ID); - } - - @Override - public void onConnected(Bundle bundle) { - LOGD(TAG, "Connected to Google API Client"); - mConnected = true; - } - - @Override - public void onConnectionSuspended(int i) { - mConnected = false; - } - - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - Log.e(TAG, "Failed to connect to the Google API client"); - mConnected = false; - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/NotificationActivity.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/NotificationActivity.java deleted file mode 100644 index 62a21466e0..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/NotificationActivity.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 com.google.samples.apps.iosched.iowear; - -import static com.google.samples.apps.iosched.iowear.utils.Utils.LOGD; -import static com.google.samples.apps.iosched.iowear.utils.Utils.makeLogTag; - -import com.google.samples.apps.iosched.R; - -import android.app.Activity; -import android.content.Intent; -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.View; -import android.widget.TextView; - -/** - * The activity that builds the notification page. - */ -public class NotificationActivity extends Activity { - - private static final String TAG = makeLogTag("NotificationActivity"); - private TextView mSession; - private TextView mSpeaker; - private View mContent; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_notification); - setupViews(); - - Bundle b = this.getIntent().getExtras(); - if (null != b) { - final String sessionId = b.getString(HomeListenerService.KEY_SESSION_ID); - LOGD(TAG, "Received session id in NotificationActivity: " + sessionId); - final String sessionName = b.getString(HomeListenerService.KEY_SESSION_NAME); - final String sessionRoom = b.getString(HomeListenerService.KEY_SESSION_ROOM); - final String speakers = getIntent() - .getStringExtra(HomeListenerService.KEY_SPEAKER_NAME); - final int notificationId = getIntent() - .getIntExtra(HomeListenerService.KEY_NOTIFICATION_ID, 0); - mSession.setText(sessionName); - mSpeaker.setText(speakers + " - " + sessionRoom); - mContent.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent feedbackIntent = new Intent(NotificationActivity.this, - PagerActivity.class); - feedbackIntent.putExtra(HomeListenerService.KEY_SESSION_ID, sessionId); - feedbackIntent - .putExtra(HomeListenerService.KEY_NOTIFICATION_ID, notificationId); - feedbackIntent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(feedbackIntent); - } - }); - } - } - - private void setupViews() { - Typeface typeFace = Typeface.createFromAsset(getAssets(), "RobotoCondensed-Light.ttf"); - mSession = (TextView) findViewById(R.id.session_name); - mSpeaker = (TextView) findViewById(R.id.speaker_room); - mContent = findViewById(R.id.content); - TextView title = (TextView) findViewById(R.id.title); - mSession.setTypeface(typeFace); - mSpeaker.setTypeface(typeFace); - title.setTypeface(typeFace); - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/OnQuestionAnsweredListener.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/OnQuestionAnsweredListener.java deleted file mode 100644 index 096ffadc86..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/OnQuestionAnsweredListener.java +++ /dev/null @@ -1,35 +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 com.google.samples.apps.iosched.iowear; - -/** - * A simple interface for answering individual questions in a session feedback or submitting the - * whole feedback. - */ -public interface OnQuestionAnsweredListener { - - /** - * Is called when a response to a question is provided. If responseNumber < 0, it means the - * answer should be removed for that question - */ - public void onQuestionAnswered(int questionNumber, int responseNumber); - - /** - * Is called when the whole feedback is ready for submission. - */ - public void submit(); -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerActivity.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerActivity.java deleted file mode 100644 index 1ab6c4ef0e..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerActivity.java +++ /dev/null @@ -1,298 +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 com.google.samples.apps.iosched.iowear; - -import static com.google.samples.apps.iosched.iowear.utils.Utils.LOGD; -import static com.google.samples.apps.iosched.iowear.utils.Utils.makeLogTag; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.wearable.DataApi; -import com.google.android.gms.wearable.DataMap; -import com.google.android.gms.wearable.PutDataMapRequest; -import com.google.android.gms.wearable.PutDataRequest; -import com.google.android.gms.wearable.Wearable; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.iowear.utils.Utils; -import com.google.samples.apps.iosched.iowear.fragments.FeedbackFragment; -import com.google.samples.apps.iosched.iowear.fragments.RadioFragment; -import com.google.samples.apps.iosched.iowear.fragments.StarFragment; -import com.google.samples.apps.iosched.iowear.fragments.SubmitFragment; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.NotificationManagerCompat; -import android.support.v4.view.ViewPager; -import android.util.Log; -import android.view.WindowManager; -import android.widget.ImageView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - -/** - * The main activity tht build all pages of a session feedback. - */ -public class PagerActivity extends Activity - implements OnQuestionAnsweredListener, GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - private static final long PAGE_FLIP_DELAY_MS = 2000; // in millis - private ViewPager mViewPager; - private static final String TAG = makeLogTag("PagerActivity"); - private List mFragments = new ArrayList(); - private Timer mTimer; - private TimerTask mTimerTask; - private Handler mHandler; - private Map responses = new HashMap(); - private GoogleApiClient mGoogleApiClient; - private String mSessionId; - private ImageView[] indicators = null; - private int[] mSavedResponses; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_pager); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - mHandler = new Handler(); - setupViews(); - - if (getIntent().hasExtra(HomeListenerService.KEY_SESSION_ID)) { - mSessionId = getIntent().getExtras().getString(HomeListenerService.KEY_SESSION_ID); - LOGD(TAG, "Session received from service: " + mSessionId); - - } - - // if there is any prior persisted answers for this feedback, lets load them. - mSavedResponses = Utils.getPersistedResponses(this, mSessionId); - Utils.saveSessionId(this, mSessionId); - - for (int i = 0; i < 4; i++) { - LOGD(TAG, "Response " + i + ": " + mSavedResponses[i]); - if (mSavedResponses[i] > -1) { - responses.put(i, mSavedResponses[i]); - } - } - - final PagerAdapter adapter = new PagerAdapter(getFragmentManager()); - StarFragment fragment0 = StarFragment.newInstance(0, mSavedResponses[0]); - RadioFragment fragment1 = RadioFragment.newInstance(1, mSavedResponses[1]); - RadioFragment fragment2 = RadioFragment.newInstance(2, mSavedResponses[2]); - RadioFragment fragment3 = RadioFragment.newInstance(3, mSavedResponses[3]); - - mFragments.add(fragment0); - mFragments.add(fragment1); - mFragments.add(fragment2); - mFragments.add(fragment3); - - for (FeedbackFragment f : mFragments) { - f.setOnQuestionListener(this); - adapter.addFragment(f); - } - - adapter.addFragment(new SubmitFragment(this)); - mViewPager.setAdapter(adapter); - - mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int i, float v, int i2) { - - } - - @Override - public void onPageSelected(int i) { - clearTimer(); - setIndicator(i); - if (i == 0) { - mFragments.get(i).reshowQuestion(); - } - } - - @Override - public void onPageScrollStateChanged(int i) { - - } - }); - - mGoogleApiClient = new GoogleApiClient.Builder(this) - .addApi(Wearable.API) - .addConnectionCallbacks(this) - .addOnConnectionFailedListener(this) - .build(); - } - - @Override - protected void onStart() { - mGoogleApiClient.connect(); - super.onStart(); - } - - @Override - protected void onStop() { - mGoogleApiClient.disconnect(); - super.onStop(); - } - - @Override - public void onQuestionAnswered(int questionNumber, int responseNumber) { - LOGD(TAG, "Question Answered: " + questionNumber + " -> " + responseNumber); - Utils.saveResponse(this, questionNumber, responseNumber); - renewTimer(questionNumber + 1); - responses.put(questionNumber, responseNumber); - } - - @Override - public void submit() { - // do the submission - Intent finishIntent = new Intent(this, FinishActivity.class); - try { - PutDataRequest dataRequest = buildDataRequest(); - if (mGoogleApiClient.isConnected()) { - Wearable.DataApi.putDataItem( - mGoogleApiClient, dataRequest) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(DataApi.DataItemResult dataItemResult) { - if (!dataItemResult.getStatus().isSuccess()) { - LOGD(TAG, "Failed to send back responses, status: " - + dataItemResult.getStatus()); - } - } - }); - } else { - Log.e(TAG, "submit() Failed to send data to phone since there was no Google API " - + "client connectivity"); - } - } catch (JSONException e) { - Log.e(TAG, "Failed to build a json from responses", e); - } - - // remove local notification upon submission - NotificationManagerCompat.from(this) - .cancel(mSessionId, HomeListenerService.NOTIFICATION_ID); - - // clear persisted local data - Utils.clearResponses(this); - startActivity(finishIntent); - finish(); - } - - /** - * Builds a {@link com.google.android.gms.wearable.PutDataRequest} which holds a JSON - * representation of the feedback collected. - */ - private PutDataRequest buildDataRequest() throws JSONException { - PutDataMapRequest putDataMapRequest = PutDataMapRequest - .create(HomeListenerService.PATH_RESPONSE); - DataMap dataMap = putDataMapRequest.getDataMap(); - - JSONArray jsonArray = new JSONArray(); - if (!responses.isEmpty()) { - JSONObject sessionObj = new JSONObject(); - sessionObj.put("s", mSessionId); - jsonArray.put(0, sessionObj); - int i = 1; - for (Integer key : responses.keySet()) { - JSONObject obj = new JSONObject(); - obj.put("q", key); - obj.put("a", responses.get(key)); - jsonArray.put(i++, obj); - } - } - String response = jsonArray.toString(); - LOGD(TAG, "JSON representation of the response: " + response); - dataMap.putString("response", response); - return putDataMapRequest.asPutDataRequest(); - } - - /** - * Renews the timer that causes pages to flip upon providing an answer. - */ - private void renewTimer(final int targetPage) { - clearTimer(); - mTimerTask = new TimerTask() { - @Override - public void run() { - mHandler.post(new Runnable() { - @Override - public void run() { - mViewPager.setCurrentItem(targetPage); - } - }); - } - }; - mTimer = new Timer(); - mTimer.schedule(mTimerTask, PAGE_FLIP_DELAY_MS); - } - - /** - * Clear the timer that flips the pages. - */ - private void clearTimer() { - if (null != mTimer) { - mTimer.cancel(); - } - } - - @Override - public void onConnected(Bundle bundle) { - LOGD(TAG, "onConnected() Connected to Google Api Service"); - } - - @Override - public void onConnectionSuspended(int i) { - - } - - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - Log.e(TAG, "onConnectionFailed(): Connection to Google Api Service failed with result: " - + connectionResult); - } - - private void setupViews() { - mViewPager = (ViewPager) findViewById(R.id.pager); - indicators = new ImageView[5]; - indicators[0] = (ImageView) findViewById(R.id.indicator_0); - indicators[1] = (ImageView) findViewById(R.id.indicator_1); - indicators[2] = (ImageView) findViewById(R.id.indicator_2); - indicators[3] = (ImageView) findViewById(R.id.indicator_3); - indicators[4] = (ImageView) findViewById(R.id.indicator_4); - } - - /** - * Sets the page indicator for the ViewPager - */ - private void setIndicator(int i) { - for (int k = 0; k < indicators.length; k++) { - indicators[k].setImageResource(i == k - ? R.drawable.page_dot_full : R.drawable.page_dot_empty); - } - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerAdapter.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerAdapter.java deleted file mode 100644 index d8282d957e..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/PagerAdapter.java +++ /dev/null @@ -1,49 +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 com.google.samples.apps.iosched.iowear; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.support.v13.app.FragmentPagerAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class PagerAdapter extends FragmentPagerAdapter { - - List mFragments = null; - - public PagerAdapter(FragmentManager fm) { - super(fm); - mFragments = new ArrayList(); - } - - @Override - public Fragment getItem(int position) { - return mFragments.get(position); - } - - @Override - public int getCount() { - return mFragments.size(); - } - - public void addFragment(Fragment fragment) { - mFragments.add(fragment); - notifyDataSetChanged(); - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/QuestionModel.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/QuestionModel.java deleted file mode 100644 index be3c29d6d0..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/QuestionModel.java +++ /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. - */ - -package com.google.samples.apps.iosched.iowear; - - -import com.google.samples.apps.iosched.R; - -import android.content.Context; - -/** - * This class encapsulates a question and its answers. - */ -public class QuestionModel { - - private String mQuestion; - private String[] mAnswers; - private int mQuestionNumber; - - public String getQuestion() { - return mQuestion; - } - - public String getAnswer(int number) { - return mAnswers[number]; - } - - public int getQuestionNumber() { - return mQuestionNumber; - } - - public QuestionModel(Context context, int questionNumber) { - int questionId = 0; - int optionsId = 0; - switch (questionNumber) { - case 0: - questionId = R.string.q0; - optionsId = R.array.q0_options; - break; - - case 1: - questionId = R.string.q1; - optionsId = R.array.q1_options; - break; - - case 2: - questionId = R.string.q2; - optionsId = R.array.q2_options; - break; - - case 3: - questionId = R.string.q3; - optionsId = R.array.q3_options; - break; - } - mAnswers = context.getResources().getStringArray(optionsId); - mQuestion = context.getString(questionId); - mQuestionNumber = questionNumber; - } - -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/Utils/Utils.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/Utils/Utils.java deleted file mode 100644 index 8ab1ef13c4..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/Utils/Utils.java +++ /dev/null @@ -1,119 +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 com.google.samples.apps.iosched.iowear.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.Log; - -/** - * A utility class. All methods are stateless and static. - */ -public class Utils { - - private static final String TAG = "Utils"; - private static final String PREF_SESSION_ID = "pref-session-id"; - private static final String[] PREF_RESPONSES = {"pref-response-0", "pref-response-1", - "pref-response-2", "pref-response-3"}; - - /** - * Returns the persisted responses for the feedback with the provided session id. The return is - * an int array of size 4. A value -1 indicates that there is no - * response for that question. - */ - public static int[] getPersistedResponses(Context context, String sessionId) { - String oldSession = getSessionIdFromPreference(context); - if (null == oldSession || !sessionId.equals(oldSession)) { - // saved data is for a different session - LOGD(TAG, "Clearing persisted data, oldSession = " + oldSession + ", new session: " - + sessionId); - clearResponses(context); - return new int[]{-1, -1, -1, -1}; - } - int[] responses = new int[4]; - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - for (int i = 0; i < PREF_RESPONSES.length; i++) { - responses[i] = pref.getInt(PREF_RESPONSES[i], -1); - } - return responses; - } - - /** - * Persists the response to a question for future retrieval. Passing a -1 for a - * response amounts to clearing the response for that question. - */ - public static void saveResponse(Context context, int question, int response) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - if (response == -1) { - // we want to remove - pref.edit().remove(PREF_RESPONSES[question]).apply(); - } else { - pref.edit().putInt(PREF_RESPONSES[question], response).apply(); - } - } - - /** - * Clears all the persisted responses. - */ - public static void clearResponses(Context context) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - pref.edit().remove(PREF_SESSION_ID).apply(); - for (String name : PREF_RESPONSES) { - pref.edit().remove(name).apply(); - } - } - - /** - * Saves the sessionId. - */ - public static void saveSessionId(Context context, String sessionId) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - if (null == sessionId) { - // we want to remove - pref.edit().remove(PREF_SESSION_ID).apply(); - } else { - pref.edit().putString(PREF_SESSION_ID, sessionId).apply(); - } - } - - /** - * Returns the persisted session id. - */ - private static String getSessionIdFromPreference(Context context) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - return pref.getString(PREF_SESSION_ID, null); - } - - /** - * A simple wrapper around LOGD - */ - public static final void LOGD(String TAG, String message) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - LOGD(TAG, message); - } - } - - public static String makeLogTag(String str) { - if (str.length() > 23) { - return str.substring(0, 23); - } - - return str; - } - -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/WearableApplication.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/WearableApplication.java deleted file mode 100644 index 7c4b18739a..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/WearableApplication.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 com.google.samples.apps.iosched.iowear; - - -import com.google.samples.apps.iosched.R; - -import android.app.Application; - -public class WearableApplication extends Application { - - public static float SMALL_TEXT_SIZE; - public static float MEDIUM_TEXT_SIZE; - public static QuestionModel[] mQuestions = new QuestionModel[4]; - - @Override - public void onCreate() { - super.onCreate(); - SMALL_TEXT_SIZE = getResources().getDimension(R.dimen.question_small_text_size); - MEDIUM_TEXT_SIZE = getResources().getDimension(R.dimen.question_medium_text_size); - mQuestions[0] = new QuestionModel(getApplicationContext(), 0); - mQuestions[1] = new QuestionModel(getApplicationContext(), 1); - mQuestions[2] = new QuestionModel(getApplicationContext(), 2); - mQuestions[3] = new QuestionModel(getApplicationContext(), 3); - - } - - public static QuestionModel getQuestion(int number) { - return mQuestions[number]; - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/FeedbackFragment.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/FeedbackFragment.java deleted file mode 100644 index 321e1c30f5..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/FeedbackFragment.java +++ /dev/null @@ -1,47 +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 com.google.samples.apps.iosched.iowear.fragments; - -import com.google.samples.apps.iosched.iowear.OnQuestionAnsweredListener; - -import android.app.Fragment; - -/** - * The base class for feedback fragments. - */ -public class FeedbackFragment extends Fragment { - - protected OnQuestionAnsweredListener mListener; - protected static final String KEY_PERSISTED_RESPONSE = "persistedResponse"; - protected static final String KEY_QUESTION = "question"; - - public void setOnQuestionListener(OnQuestionAnsweredListener listener) { - if (null != listener) { - this.mListener = listener; - } - } - - public void removeOnQuestionListener() { - this.mListener = null; - } - - public void reshowQuestion(){ - // do nothing, let the subclasses handle - } - - -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/RadioFragment.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/RadioFragment.java deleted file mode 100644 index 8de28aacb1..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/RadioFragment.java +++ /dev/null @@ -1,132 +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 com.google.samples.apps.iosched.iowear.fragments; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.iowear.QuestionModel; -import com.google.samples.apps.iosched.iowear.WearableApplication; - -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.TextView; - -/** - * The fragment that shows a question along with 5 responses for user to choose from. - */ -public class RadioFragment extends FeedbackFragment{ - - private TextView mQuestionView; - private Typeface mTypeFace; - private QuestionModel mQuestion; - private RadioButton[] mRadioButtons = new RadioButton[5]; - private RadioGroup mRadioGroup; - private int mPersistedAnswer; - - public static RadioFragment newInstance(int questionNumber, int persistedResponse) { - RadioFragment fragment = new RadioFragment(); - Bundle b = new Bundle(); - b.putInt(KEY_QUESTION, questionNumber); - b.putInt(KEY_PERSISTED_RESPONSE, persistedResponse); - fragment.setArguments(b); - return fragment; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mTypeFace = Typeface - .createFromAsset(getActivity().getAssets(), "RobotoCondensed-Light.ttf"); - View view = inflater.inflate(R.layout.fragment_generic_question, container, false); - mQuestion = WearableApplication.getQuestion(getArguments().getInt(KEY_QUESTION)); - mPersistedAnswer = getArguments().getInt(KEY_PERSISTED_RESPONSE); - setupViews(view); - loadTexts(); - return view; - } - - private void setupViews(View view) { - mQuestionView = (TextView) view.findViewById(R.id.question); - mRadioGroup = (RadioGroup) view.findViewById(R.id.radioGroup); - mRadioButtons[0] = (RadioButton) view.findViewById(R.id.radioButton1); - mRadioButtons[1] = (RadioButton) view.findViewById(R.id.radioButton2); - mRadioButtons[2] = (RadioButton) view.findViewById(R.id.radioButton3); - mRadioButtons[3] = (RadioButton) view.findViewById(R.id.radioButton4); - mRadioButtons[4] = (RadioButton) view.findViewById(R.id.radioButton5); - for (RadioButton radio : mRadioButtons) { - radio.setTypeface(mTypeFace); - } - if (mPersistedAnswer > -1) { - // we have a persisted response - mRadioButtons[mPersistedAnswer].setChecked(true); - } - mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(RadioGroup radioGroup, int i) { - int answer = -1; - switch(i) { - case R.id.radioButton1: - answer = 0; - break; - case R.id.radioButton2: - answer = 1; - break; - case R.id.radioButton3: - answer = 2; - break; - case R.id.radioButton4: - answer = 3; - break; - case R.id.radioButton5: - answer = 4; - break; - } - mListener.onQuestionAnswered(mQuestion.getQuestionNumber(), answer); - } - }); - } - - private void loadTexts(){ - String[] options = null; - String question = null; - switch (mQuestion.getQuestionNumber()) { - case 1: - options = getResources().getStringArray(R.array.q1_options); - question = getString(R.string.q1); - break; - - case 2: - options = getResources().getStringArray(R.array.q2_options); - question = getString(R.string.q2); - break; - - case 3: - options = getResources().getStringArray(R.array.q3_options); - question = getString(R.string.q3); - break; - } - mQuestionView.setTypeface(mTypeFace, Typeface.ITALIC); - mQuestionView.setText(question); - for (int i=0; i < options.length; i++) { - mRadioButtons[i].setText(options[i]); - } - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/StarFragment.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/StarFragment.java deleted file mode 100644 index 734b4c6a68..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/StarFragment.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 com.google.samples.apps.iosched.iowear.fragments; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.iowear.QuestionModel; -import com.google.samples.apps.iosched.iowear.WearableApplication; - -import android.graphics.Typeface; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RatingBar; -import android.widget.TextView; - -/** - * The fragment that builds the Overall Rating page. - */ -public class StarFragment extends FeedbackFragment { - - private TextView mQuestionView; - private RatingBar mRatingBar; - private Typeface mTypeFace; - private QuestionModel mQuestion; - private boolean mInitializing; - - public static StarFragment newInstance(int questionNumber, int persistedResponse) { - StarFragment fragment = new StarFragment(); - Bundle b = new Bundle(); - b.putInt(KEY_QUESTION, questionNumber); - b.putInt(KEY_PERSISTED_RESPONSE, persistedResponse); - fragment.setArguments(b); - return fragment; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mTypeFace = Typeface - .createFromAsset(getActivity().getAssets(), "RobotoCondensed-Light.ttf"); - View view = inflater.inflate(R.layout.fragment_star, container, false); - mQuestion = WearableApplication.getQuestion(getArguments().getInt(KEY_QUESTION)); - setupViews(view); - loadTexts(); - return view; - } - - private void setupViews(View view) { - mQuestionView = (TextView) view.findViewById(R.id.question); - mRatingBar = (RatingBar) view.findViewById(R.id.ratingbar); - mRatingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() { - @Override - public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) { - int ratingInt = (int) rating - 1; - setQandA(ratingInt); - if (!mInitializing) { - mListener.onQuestionAnswered(mQuestion.getQuestionNumber(), ratingInt); - mInitializing = false; - } - } - }); - mRatingBar.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - int ratingInt = (int) mRatingBar.getRating() - 1; - setQandA(ratingInt); - return false; - } - }); - int persistedResponse = getArguments().getInt(KEY_PERSISTED_RESPONSE); - if(persistedResponse > -1) { - mInitializing = true; - mRatingBar.setRating(persistedResponse + 1); - } - } - - private void loadTexts() { - setQandA(-1); - mQuestionView.setTypeface(mTypeFace, Typeface.ITALIC); - } - - private void setQandA(int answer) { - mQuestionView.setText(answer < 0 ? mQuestion.getQuestion() : mQuestion.getAnswer(answer)); - mQuestionView.setTextSize(TypedValue.COMPLEX_UNIT_PX, - answer < 0 ? WearableApplication.SMALL_TEXT_SIZE - : WearableApplication.MEDIUM_TEXT_SIZE); - } - - public void removeOnQuestionListener() { - this.mListener = null; - } - - public void reshowQuestion(){ - setQandA(-1); - } -} diff --git a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/SubmitFragment.java b/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/SubmitFragment.java deleted file mode 100644 index 30cc2aa35d..0000000000 --- a/Wearable/src/main/java/com/google/samples/apps/iosched/iowear/fragments/SubmitFragment.java +++ /dev/null @@ -1,58 +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 com.google.samples.apps.iosched.iowear.fragments; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.iowear.OnQuestionAnsweredListener; - -import android.app.Fragment; -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -/** - * Te fragment that builds the submission page. - */ -public class SubmitFragment extends Fragment { - - private final OnQuestionAnsweredListener mListener; - - public SubmitFragment(OnQuestionAnsweredListener listener) { - this.mListener = listener; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_submit, container, false); - Typeface typeFace = Typeface - .createFromAsset(getActivity().getAssets(), "RobotoCondensed-Light.ttf"); - view.findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mListener.submit(); - } - }); - TextView submitText = (TextView) view.findViewById(R.id.submit_text); - submitText.setTypeface(typeFace); - return view; - } - -} diff --git a/Wearable/src/main/res/drawable-xhdpi/ic_action_done.png b/Wearable/src/main/res/drawable-xhdpi/ic_action_done.png deleted file mode 100644 index b7bc75859e..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/ic_action_done.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/ic_action_send.png b/Wearable/src/main/res/drawable-xhdpi/ic_action_send.png deleted file mode 100644 index fb47fb2886..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/ic_action_send.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png b/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 58d4a6e8e8..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/radio_off.png b/Wearable/src/main/res/drawable-xhdpi/radio_off.png deleted file mode 100644 index 0e29703d46..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/radio_off.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/radio_on.png b/Wearable/src/main/res/drawable-xhdpi/radio_on.png deleted file mode 100644 index 759ccc0a4d..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/radio_on.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/star_off.png b/Wearable/src/main/res/drawable-xhdpi/star_off.png deleted file mode 100644 index 136766d415..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/star_off.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable-xhdpi/star_on.png b/Wearable/src/main/res/drawable-xhdpi/star_on.png deleted file mode 100644 index 9cbc08e547..0000000000 Binary files a/Wearable/src/main/res/drawable-xhdpi/star_on.png and /dev/null differ diff --git a/Wearable/src/main/res/drawable/action_button_background.xml b/Wearable/src/main/res/drawable/action_button_background.xml deleted file mode 100644 index 0f88d8c8a4..0000000000 --- a/Wearable/src/main/res/drawable/action_button_background.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/drawable/conference_rating_bar.xml b/Wearable/src/main/res/drawable/conference_rating_bar.xml deleted file mode 100644 index 9145f09b49..0000000000 --- a/Wearable/src/main/res/drawable/conference_rating_bar.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/Wearable/src/main/res/drawable/conference_rating_bar_empty.xml b/Wearable/src/main/res/drawable/conference_rating_bar_empty.xml deleted file mode 100644 index 7cc5c86a39..0000000000 --- a/Wearable/src/main/res/drawable/conference_rating_bar_empty.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/drawable/conference_rating_bar_full.xml b/Wearable/src/main/res/drawable/conference_rating_bar_full.xml deleted file mode 100644 index 92271b1302..0000000000 --- a/Wearable/src/main/res/drawable/conference_rating_bar_full.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/drawable/page_dot_empty.xml b/Wearable/src/main/res/drawable/page_dot_empty.xml deleted file mode 100644 index e26bf61382..0000000000 --- a/Wearable/src/main/res/drawable/page_dot_empty.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - diff --git a/Wearable/src/main/res/drawable/page_dot_full.xml b/Wearable/src/main/res/drawable/page_dot_full.xml deleted file mode 100644 index a144242da7..0000000000 --- a/Wearable/src/main/res/drawable/page_dot_full.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/Wearable/src/main/res/drawable/radio_indicator.xml b/Wearable/src/main/res/drawable/radio_indicator.xml deleted file mode 100644 index a8e6615b13..0000000000 --- a/Wearable/src/main/res/drawable/radio_indicator.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/Wearable/src/main/res/drawable/success_icon_background.xml b/Wearable/src/main/res/drawable/success_icon_background.xml deleted file mode 100644 index 54d8d3f45f..0000000000 --- a/Wearable/src/main/res/drawable/success_icon_background.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/Wearable/src/main/res/layout/activity_finish.xml b/Wearable/src/main/res/layout/activity_finish.xml deleted file mode 100644 index 687c556ff0..0000000000 --- a/Wearable/src/main/res/layout/activity_finish.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - diff --git a/Wearable/src/main/res/layout/activity_notification.xml b/Wearable/src/main/res/layout/activity_notification.xml deleted file mode 100644 index d26c4ed977..0000000000 --- a/Wearable/src/main/res/layout/activity_notification.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/layout/activity_pager.xml b/Wearable/src/main/res/layout/activity_pager.xml deleted file mode 100644 index 8ad1f2e700..0000000000 --- a/Wearable/src/main/res/layout/activity_pager.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/layout/fragment_generic_question.xml b/Wearable/src/main/res/layout/fragment_generic_question.xml deleted file mode 100644 index 8c90f6d4db..0000000000 --- a/Wearable/src/main/res/layout/fragment_generic_question.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Wearable/src/main/res/layout/fragment_star.xml b/Wearable/src/main/res/layout/fragment_star.xml deleted file mode 100644 index 29e4c1d552..0000000000 --- a/Wearable/src/main/res/layout/fragment_star.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - diff --git a/Wearable/src/main/res/layout/fragment_submit.xml b/Wearable/src/main/res/layout/fragment_submit.xml deleted file mode 100644 index 27e37ec59c..0000000000 --- a/Wearable/src/main/res/layout/fragment_submit.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - diff --git a/Wearable/src/main/res/values-de/strings.xml b/Wearable/src/main/res/values-de/strings.xml deleted file mode 100644 index 161c7f24ce..0000000000 --- a/Wearable/src/main/res/values-de/strings.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Ihr Gesamteindruck dieser Sitzung: - - - - 1 Stern! - 2 Sterne - 3 Sterne - 4 Sterne - 5 Sterne - - - - Wie nützlich war diese Sitzung für Sie? - - - - Gar nicht - Ein wenig - Eher nützlich - Sehr nützlich - Extrem nützlich - - - - Die Inhalte waren: - - - - Zu grundlegend - Einfach - Auf den Punkt - Fortgeschritten - Zu fortgeschritten - - - - Qualität des Sprechers / der Sprecherin: - - - Absenden - - - Vielen Dank! - - - Bewerten Sie diese Sitzung - - - - Schlecht - Ausreichend - Durchschnittlich - Gut - Ausgezeichnet - - diff --git a/Wearable/src/main/res/values-es/strings.xml b/Wearable/src/main/res/values-es/strings.xml deleted file mode 100644 index 850a7ac710..0000000000 --- a/Wearable/src/main/res/values-es/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - Valore de manera general esta sesión: - - - - 1 estrella - 2 estrellas - 3 estrellas - 4 estrellas - 5 estrellas - - - - ¿Le ha resultado relevante esta sesión? - - - - En absoluto - Ligeramente - Moderadamente - Mucho - Muchísimo - - - - El contenido le ha parecido: - - - - Demasiado básico - Básico - Perfecto - Avanzado - Demasiado avanzado - - - - Nivel del ponente: - - - Enviar - - - ¡Gracias! - - - Valore esta sesión - - - - Bajo - Aceptable - Medio - Bueno - Excelente - - diff --git a/Wearable/src/main/res/values-fr/strings.xml b/Wearable/src/main/res/values-fr/strings.xml deleted file mode 100644 index 59eec92594..0000000000 --- a/Wearable/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - Évaluez cette session dans son ensemble : - - - - 1 étoile ! - 2 étoiles - 3 étoiles - 4 étoiles - 5 étoiles - - - - Cette session vous a-t-elle semblé appropriée ? - - - - Pas du tout - Un peu - Modérément - Très - Extrêmement - - - - Le contenu était : - - - - Trop rudimentaire - Rudimentaire - Adapté - Avancé - Trop avancé - - - - Qualité de l\'intervenant : - - - Soumettre - - - Merci ! - - - Évaluer cette session - - - - Mauvaise - Passable - Moyenne - Bonne - Excellente - - diff --git a/Wearable/src/main/res/values-it/strings.xml b/Wearable/src/main/res/values-it/strings.xml deleted file mode 100644 index aad19776db..0000000000 --- a/Wearable/src/main/res/values-it/strings.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Valuta questa sessione in generale: - - - - 1 stella! - 2 stelle - 3 stelle - 4 stelle - 5 stelle - - - - Quanto era pertinente questa sessione? - - - - Per niente - Poco - Moderatamente - Molto - Estremamente - - - - Il contenuto era: - - - - Troppo elementare - Elementare - Focalizzato - Avanzato - Troppo avanzato - - - - Qualità dell\'oratore: - - - Invia - - - Grazie! - - - Valuta questa sessione - - - - Scadente - Modesta - Media - Buona - Eccellente - - diff --git a/Wearable/src/main/res/values-ja/strings.xml b/Wearable/src/main/res/values-ja/strings.xml deleted file mode 100644 index f8131a1d84..0000000000 --- a/Wearable/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³å…¨èˆ¬ã‚’評価ã—ã¦ãã ã•ã„。 - - - - 1ã¤æ˜Ÿï¼ - 2ã¤æ˜Ÿï¼ - 3ã¤æ˜Ÿï¼ - 4ã¤æ˜Ÿï¼ - 5ã¤æ˜Ÿï¼ - - - - ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã¯ã©ã‚Œãらã„å½¹ã ã¡ã¾ã—ãŸã‹ï¼Ÿ - - - - å…¨ãå½¹ã«ãŸãŸãªã‹ã£ãŸã€‚ - å°‘ã—å½¹ã«ãŸã£ãŸã€‚ - ã¾ã‚ã¾ã‚å½¹ã ã£ãŸã€‚ - ã¨ã¦ã‚‚å½¹ã ã£ãŸã€‚ - éžå¸¸ã«å½¹ã ã£ãŸã€‚ - - - - ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã®å†…容ã¯ã€ - - - - 基本的ã™ãŽãŸã€‚ - 基本的ã ã£ãŸã€‚ - ã´ã£ãŸã‚Šã ã£ãŸã€‚ - 高度ã ã£ãŸã€‚ - 高度ã™ãŽãŸã€‚ - - - - è©±ã—æ‰‹ã®ã‚¯ã‚ªãƒªãƒ†ã‚£â€•ã¯ã„ã‹ãŒã§ã—ãŸã‹? - - - é€ä¿¡ - - - ã‚りãŒã¨ã†ã”ã–ã„ã¾ã—ãŸï¼ - - - ã“ã®ã‚»ãƒƒã‚·ãƒ§ãƒ³ã‚’評価ã—ã¦ãã ã•ã„ - - - - 良ããªã‹ã£ãŸ - ã¾ã‚ã¾ã‚ - å¹³å‡çš„ - 良ã‹ã£ãŸ - 大変良ã‹ã£ãŸ - - diff --git a/Wearable/src/main/res/values-ko/strings.xml b/Wearable/src/main/res/values-ko/strings.xml deleted file mode 100644 index fc56c0da15..0000000000 --- a/Wearable/src/main/res/values-ko/strings.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - 종합ì ìœ¼ë¡œ 세션 í‰ê°€í•˜ê¸°: - - - - 별 1ê°œ! - 별 2ê°œ - 별 3ê°œ - 별 4ê°œ - 별 5ê°œ - - - - ì„¸ì…˜ì´ ì–¼ë§ˆë‚˜ ì ì ˆí–ˆìŠµë‹ˆê¹Œ? - - - - 전혀 - 약간 - 보통 - 매우 - 대단히 - - - - 세션 ë‚´ìš©: - - - - 너무 초급 수준 - 초급 수준 - ë”± ë§žìŒ - 고급 수준 - 너무 고급 수준 - - - - ë°œí‘œìž ìˆ˜ì¤€: - - - 제출 - - - ê°ì‚¬í•©ë‹ˆë‹¤! - - - 세션 í‰ê°€í•˜ê¸° - - - - í˜•íŽ¸ì—†ìŒ - 그저 그럼 - 보통 - ì¢‹ìŒ - 뛰어남 - - diff --git a/Wearable/src/main/res/values-pt/strings.xml b/Wearable/src/main/res/values-pt/strings.xml deleted file mode 100644 index a5b58985c4..0000000000 --- a/Wearable/src/main/res/values-pt/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - Classifique esta sessão em termos gerais: - - - - 1 Estrela! - 2 Estrelas - 3 Estrelas - 4 Estrelas - 5 Estrelas - - - - Quão relevante foi essa sessão? - - - - Nenhum pouco - Um pouco - Moderadamente - Muito - Extremamente - - - - O conteúdo foi: - - - - Muito básico - Básico - Preciso - Avançado - Muito avançado - - - - Qualidade do orador: - - - Enviar - - - Obrigado(a)! - - - Classifique esta sessão - - - - Ruim - Razoável - Mediana - Boa - Excelente - - diff --git a/Wearable/src/main/res/values-ru/strings.xml b/Wearable/src/main/res/values-ru/strings.xml deleted file mode 100644 index f25399ad61..0000000000 --- a/Wearable/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - Оцените Ñту тематичеÑкую Ñекцию в целом: - - - - 1 звезда! - 2 звезды - 3 звезды - 4 звезды - 5 звезд - - - - ÐаÑколько актуальной была Ñта тематичеÑÐºÐ°Ñ ÑекциÑ? - - - - СовÑем неактуальной - Ðемного актуальной - Умеренно актуальной - Очень актуальной - Чрезвычайно актуальной - - - - Содержание было: - - - - Слишком общим - Общим - Таким, как нужно - Сложным - Слишком Ñложным - - - - КачеÑтво речи выÑтупающего: - - - Отправить - - - СпаÑибо! - - - Оцените Ñту тематичеÑкую Ñекцию - - - - Плохое - Удовлетворительное - Среднее - Хорошее - ВыдающееÑÑ - - diff --git a/Wearable/src/main/res/values-zh-rCN/strings.xml b/Wearable/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index 8d572f5019..0000000000 --- a/Wearable/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - 为此次会议的总体表现评级: - - - - 1æ˜Ÿï¼ - 2星 - 3星 - 4星 - 5星 - - - - 此次会议与您的相关性如何? - - - - 根本ä¸ç›¸å…³ - 有点相关 - 适度相关 - éžå¸¸ç›¸å…³ - æžåº¦ç›¸å…³ - - - - 内容上: - - - - 过于åˆçº§ - åˆçº§ - åˆé€‚ - 高级 - 过于高级 - - - - å‘言人质é‡ï¼š - - - æäº¤ - - - è°¢è°¢ï¼ - - - 为此次会议评级 - - - - å·® - åŠæ ¼ - 中 - 良 - 优 - - diff --git a/Wearable/src/main/res/values-zh-rTW/strings.xml b/Wearable/src/main/res/values-zh-rTW/strings.xml deleted file mode 100644 index 65500125f1..0000000000 --- a/Wearable/src/main/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - å°æ­¤æ¬¡æœƒè­°é€²è¡Œæ•´é«”評級: - - - - 1æ˜Ÿï¼ - 2星 - 3星 - 4星 - 5星 - - - - 此次會議相關性如何? - - - - 完全ä¸ç›¸é—œ - 有點相關 - 相關 - éžå¸¸ç›¸é—œ - 極為相關 - - - - 會議內容: - - - - 太éŽåŸºç¤Ž - 基礎 - éžå¸¸é©åˆ - 高級 - 太éŽé«˜ç´š - - - - 演講者å“質: - - - æäº¤ - - - éžå¸¸æ„Ÿè¬ï¼ - - - å°æ­¤æ¬¡æœƒè­°è©•ç´š - - - - å·® - åŠæ ¼ - 中 - 良 - 優 - - diff --git a/Wearable/src/main/res/values/colors.xml b/Wearable/src/main/res/values/colors.xml deleted file mode 100644 index b29072b4ec..0000000000 --- a/Wearable/src/main/res/values/colors.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - #ffffff - #3f51b5 - #303f9f - #7986cb - - #00b0ff - #8ad4fa - #e91e63 - - @color/theme_primary - #cccccc - #cfff - #000 - - #434343 - diff --git a/Wearable/src/main/res/values/dimens.xml b/Wearable/src/main/res/values/dimens.xml deleted file mode 100644 index 90f856fe7b..0000000000 --- a/Wearable/src/main/res/values/dimens.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - 16dp - 16dp - - 12sp - 35sp - 29sp - 20sp - 20sp - 18sp - 20sp - 14sp - 92dp - - diff --git a/Wearable/src/main/res/values/ratingbar_style.xml b/Wearable/src/main/res/values/ratingbar_style.xml deleted file mode 100644 index f31f05e071..0000000000 --- a/Wearable/src/main/res/values/ratingbar_style.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/Wearable/src/main/res/values/strings.xml b/Wearable/src/main/res/values/strings.xml deleted file mode 100644 index dc2eb2a1a7..0000000000 --- a/Wearable/src/main/res/values/strings.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - IOWear - - - Rate this session overall: - - - - 1 Star! - 2 Stars - 3 Stars - 4 Stars - 5 Stars - - - - How relevant was this session? - - - - Not at all - Slightly - Moderately - Very - Extremely - - - - The content was: - - - - Too Basic - Basic - Spot on - Advanced - Too Advanced - - - - Speaker quality: - - - Submit - - - Thanks! - - - Rate this session - - - - Poor - Fair - Average - Good - Outstanding - - - diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index f7eb89fe2a..0000000000 --- a/android/build.gradle +++ /dev/null @@ -1,106 +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. - */ - -lpreview = hasProperty('lpreview') - -apply plugin: 'android' - -repositories { - mavenCentral() - flatDir { - dirs 'libs' - } -} - -android { - compileSdkVersion lpreview ? "android-L" : 19 - buildToolsVersion "19.1.0" - - productFlavors { lpreview ? lpreview{} : classic{} } - - defaultConfig lpreview ? {} : { - // Non-L-preview configuration - minSdkVersion 14 - targetSdkVersion 19 - } - - signingConfigs { - debug { - storeFile file("../android/debug.keystore") - storePassword "android" - keyAlias "androiddebugkey" - keyPassword "android" - } - release { - storeFile file("../android/debug.keystore") - storePassword "android" - keyAlias "androiddebugkey" - keyPassword "android" - } - } - - buildTypes { - debug { - debuggable true - signingConfig signingConfigs.debug - } - release { - runProguard true - proguardFiles getDefaultProguardFile('proguard-android.txt'), file('proguard-project.txt') - signingConfig signingConfigs.release - } - } - - lintOptions { - abortOnError false - } - - aaptOptions { - noCompress 'apk' - } -} - -dependencies { - wearApp project(':Wearable') - compile 'com.google.android.gms:play-services:5+' - compile 'com.android.support:support-v13:20.+' - compile 'com.android.support:support-v4:20.+' - compile 'com.google.android.apps.dashclock:dashclock-api:+' - compile 'com.google.code.gson:gson:2.+' - compile('com.google.api-client:google-api-client:1.+') { - exclude group: 'xpp3', module: 'shared' - exclude group: 'org.apache.httpcomponents', module: 'httpclient' - exclude group: 'junit', module: 'junit' - exclude group: 'com.google.android', module: 'android' - } - compile 'com.google.api-client:google-api-client-android:1.17.+' - compile 'com.google.apis:google-api-services-plus:+' - compile 'com.github.japgolly.android:svg-android:2.0.6' - compile fileTree(dir: 'libs', include: '*.jar') - compile files('../third_party/glide/library/libs/glide-3.2.0a.jar') - compile files('../third_party/basic-http-client/libs/basic-http-client-android-0.88.jar') - - // Google Maps Android API utility library - compile('com.google.maps.android:android-maps-utils:0.3+') { - exclude group: "com.google.android.gms" - } - - /* The two dependencies below are only required while we are using http - Drive appdata. When we start using AppData through GMS, - they should be removed. */ - compile 'com.google.http-client:google-http-client-gson:+' - compile 'com.google.apis:google-api-services-drive:+' -} diff --git a/android/libs/YouTubeAndroidPlayerApi.jar b/android/libs/YouTubeAndroidPlayerApi.jar deleted file mode 100644 index c7825a7571..0000000000 Binary files a/android/libs/YouTubeAndroidPlayerApi.jar and /dev/null differ diff --git a/android/libs/gcm.jar b/android/libs/gcm.jar deleted file mode 100644 index 6aa21c1a79..0000000000 Binary files a/android/libs/gcm.jar and /dev/null differ diff --git a/android/libs/licenses/YouTubeAndroidPlayerApi.jar.LICENSE b/android/libs/licenses/YouTubeAndroidPlayerApi.jar.LICENSE deleted file mode 100644 index 885ad74a34..0000000000 --- a/android/libs/licenses/YouTubeAndroidPlayerApi.jar.LICENSE +++ /dev/null @@ -1,3 +0,0 @@ -Copyright 2012 Google, Inc. All rights reserved. - -YouTube Android Player API diff --git a/android/libs/licenses/basic-http-client.jar.LICENSE b/android/libs/licenses/basic-http-client.jar.LICENSE deleted file mode 100644 index 24eea9d849..0000000000 --- a/android/libs/licenses/basic-http-client.jar.LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2012 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. diff --git a/android/libs/licenses/gcm.LICENSE b/android/libs/licenses/gcm.LICENSE deleted file mode 100644 index 00b1dffd1b..0000000000 --- a/android/libs/licenses/gcm.LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2012 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. - */ diff --git a/android/libs/licenses/libGoogleAnalyticsV2.jar.LICENSE b/android/libs/licenses/libGoogleAnalyticsV2.jar.LICENSE deleted file mode 100644 index 84f4bcd74a..0000000000 --- a/android/libs/licenses/libGoogleAnalyticsV2.jar.LICENSE +++ /dev/null @@ -1,3 +0,0 @@ -Google Analytics Android SDK version 2.0 beta 4 - -Copyright 2009 - 2013 Google, Inc. All rights reserved. \ No newline at end of file diff --git a/android/libs/licenses/svg-android.jar.LICENSE b/android/libs/licenses/svg-android.jar.LICENSE deleted file mode 100644 index 79b107bbfe..0000000000 --- a/android/libs/licenses/svg-android.jar.LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -SVG Android -Copyright 2011 Larva Labs LLC and Google, Inc. - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). - ---- - -Apache Batik -Copyright 1999-2007 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). - -This software contains code from the World Wide Web Consortium (W3C) for the -Document Object Model API (DOM API) and SVG Document Type Definition (DTD). - -This software contains code from the International Organisation for -Standardization for the definition of character entities used in the software's -documentation. - -This product includes images from the Tango Desktop Project -(http://tango.freedesktop.org/). - -This product includes images from the Pasodoble Icon Theme -(http://www.jesusda.com/projects/pasodoble). diff --git a/android/libs/licenses/volley.jar.LICENSE b/android/libs/licenses/volley.jar.LICENSE deleted file mode 100644 index 12f1016df0..0000000000 --- a/android/libs/licenses/volley.jar.LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (C) 2011 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/android/proguard-project.txt b/android/proguard-project.txt deleted file mode 100644 index f35fcb9e17..0000000000 --- a/android/proguard-project.txt +++ /dev/null @@ -1,128 +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 a configuration file for ProGuard. -# http://proguard.sourceforge.net/index.html#manual/usage.html --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --verbose -# Optimization is turned off by default. Dex does not like code run -# through the ProGuard optimize and preverify steps (and performs some -# of these optimizations on its own). --dontoptimize --dontpreverify - -# If you want to enable optimization, you should include the -# following: -# -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* -# -optimizationpasses 5 -# -allowaccessmodification -# -# Note that you cannot just include these flags in your own -# configuration file; if you are including this file, optimization -# will be turned off. You'll need to either edit this file, or -# duplicate the contents of this file and remove the include of this -# file from your project's proguard.config path property. --keepattributes *Annotation* --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgent --keep public class * extends android.preference.Preference --keep public class * extends android.support.v4.app.Fragment --keep public class * extends android.app.Fragment -# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native --keepclasseswithmembernames class * { - native ; -} --keep public class * extends android.view.View { - public (android.content.Context); - public (android.content.Context, android.util.AttributeSet); - public (android.content.Context, android.util.AttributeSet, int); - public void set*(...); -} --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet); -} --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); -} --keepclassmembers class * extends android.app.Activity { - public void *(android.view.View); -} -# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} --keepclassmembers class **.R$* { - public static ; -} -# The support library contains references to newer platform versions. -# Don't warn about those in case this app is linking against an older -# platform version. We know about them, and they are safe. --dontwarn android.support.** -# Needed by google-http-client to keep generic types and @Key annotations accessed via reflection --keepclassmembers class * { - @com.google.api.client.util.Key ; -} -# Needed just to be safe in terms of keeping Google API service model classes --keep class com.google.api.services.*.model.* --keep class com.google.api.client.** --keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault -# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI --dontwarn com.google.common.collect.MinMaxPriorityQueue --dontobfuscate -# Assume dependency libraries Just Work(TM) --dontwarn com.google.android.youtube.** --dontwarn com.google.android.analytics.** --dontwarn com.google.common.** -# Don't discard Guava classes that raise warnings --keep class com.google.common.collect.MapMakerInternalMap$ReferenceEntry --keep class com.google.common.cache.LocalCache$ReferenceEntry -# Make sure that Google Analytics doesn't get removed --keep class com.google.analytics.tracking.android.CampaignTrackingReceiver -## BEGIN -- Google Play Services proguard.txt --keep class * extends java.util.ListResourceBundle { - protected java.lang.Object[][] getContents(); -} -# Keep SafeParcelable value, needed for reflection. This is required to support backwards -# compatibility of some classes. --keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { - public static final *** NULL; -} -# Keep the names of classes/members we need for client functionality. --keepnames @com.google.android.gms.common.annotation.KeepName class * --keepclassmembernames class * { - @com.google.android.gms.common.annotation.KeepName *; -} -# Needed for Parcelable/SafeParcelable Creators to not get stripped --keepnames class * implements android.os.Parcelable { - public static final ** CREATOR; -} -## END -- Google Play Services proguard.txt -# Other settings --keep class com.android.** --keep class com.google.android.** --keep class com.google.android.gms.** --keep class com.google.android.gms.location.** --keep class com.google.api.client.** --keep class com.google.maps.android.** --keep class libcore.** diff --git a/android/src/classic/java/com/google/samples/apps/iosched/util/LPreviewUtils.java b/android/src/classic/java/com/google/samples/apps/iosched/util/LPreviewUtils.java deleted file mode 100644 index e9b622d9ba..0000000000 --- a/android/src/classic/java/com/google/samples/apps/iosched/util/LPreviewUtils.java +++ /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. - */ - -package com.google.samples.apps.iosched.util; - -import android.app.Activity; - -public class LPreviewUtils { - private LPreviewUtils() { - } - - public static LPreviewUtilsBase getInstance(Activity activity) { - return new LPreviewUtilsBase(activity); - } -} diff --git a/android/src/debug/res/values/placeholder_strings.xml b/android/src/debug/res/values/placeholder_strings.xml deleted file mode 100644 index 367533d822..0000000000 --- a/android/src/debug/res/values/placeholder_strings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 6/25 - 10:00am - to 10:45am - What\'s New in Android Developer Tools - Trevor Johns and Roman Nurik - Trevor Johns and Roman Nurik\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec eleifend arcu, eu convallis nisi. Etiam bibendum, tellus nec ultricies suscipit, metus justo fringilla urna, non mollis tortor libero non enim. Mauris sit amet venenatis risus, at ullamcorper ligula. Maecenas adipiscing non odio ut hendrerit. Phasellus pharetra id sapien laoreet sagittis. Etiam mattis bibendum elit. Vestibulum rutrum a libero eget facilisis. Duis tempor quam velit, at viverra tellus faucibus ut. Mauris venenatis odio sed lorem dignissim, vitae tristique arcu sagittis. - Room 10 - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec eleifend arcu, eu convallis nisi. Etiam bibendum, tellus nec ultricies suscipit, metus justo fringilla urna, non mollis tortor libero non enim. Mauris sit amet venenatis risus, at ullamcorper ligula. Maecenas adipiscing non odio ut hendrerit. Phasellus pharetra id sapien laoreet sagittis. Etiam mattis bibendum elit. Vestibulum rutrum a libero eget facilisis. Duis tempor quam velit, at viverra tellus faucibus ut. Mauris venenatis odio sed lorem dignissim, vitae tristique arcu sagittis. - No, thanks - Watch now! - diff --git a/android/src/lpreview/java/com/google/samples/apps/iosched/ui/widget/AddToScheduleFABFrameLayout.java b/android/src/lpreview/java/com/google/samples/apps/iosched/ui/widget/AddToScheduleFABFrameLayout.java deleted file mode 100644 index 88b8a45545..0000000000 --- a/android/src/lpreview/java/com/google/samples/apps/iosched/ui/widget/AddToScheduleFABFrameLayout.java +++ /dev/null @@ -1,105 +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 com.google.samples.apps.iosched.ui.widget; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.drawable.RippleDrawable; -import android.os.Build; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.ViewGroup; - -import com.google.samples.apps.iosched.R; - -@TargetApi(Build.VERSION_CODES.L) -public class AddToScheduleFABFrameLayout extends CheckableFrameLayout { - private View mRevealView; - private float mHotSpotX, mHotSpotY; - private int mRevealViewOffColor; - - public AddToScheduleFABFrameLayout(Context context) { - this(context, null, 0, 0); - } - - public AddToScheduleFABFrameLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0, 0); - } - - public AddToScheduleFABFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public AddToScheduleFABFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr); - - mRevealView = new View(context); - mRevealView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - addView(mRevealView, 0); - mRevealViewOffColor = getResources().getColor(R.color.theme_accent_1); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mHotSpotX = event.getX(); - mHotSpotY = event.getY(); - } - return super.onTouchEvent(event); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - Outline outline = new Outline(); - outline.setOval(0, 0, w, h); - setOutline(outline); - setClipToOutline(true); - } - - @Override - public void setChecked(boolean checked, boolean allowAnimate) { - super.setChecked(checked, allowAnimate); - if (allowAnimate) { - ValueAnimator animator = ViewAnimationUtils.createCircularReveal( - mRevealView, - (int) mHotSpotX, (int) mHotSpotY, 0, getWidth()); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setChecked(mChecked, false); - } - }); - animator.start(); - mRevealView.setVisibility(View.VISIBLE); - mRevealView.setBackgroundColor(mChecked ? Color.WHITE : mRevealViewOffColor); - } else { - mRevealView.setVisibility(View.GONE); - RippleDrawable newBackground = (RippleDrawable) getResources().getDrawable(mChecked - ? R.drawable.add_schedule_fab_ripple_background_on - : R.drawable.add_schedule_fab_ripple_background_off); - setBackground(newBackground); - } - } -} diff --git a/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtils.java b/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtils.java deleted file mode 100644 index 6503430cb8..0000000000 --- a/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtils.java +++ /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. - */ - -package com.google.samples.apps.iosched.util; - -import android.app.Activity; -import android.os.Build; - -public class LPreviewUtils { - private LPreviewUtils() { - } - - public static LPreviewUtilsBase getInstance(Activity activity) { - if ("L".equals(Build.VERSION.CODENAME)) { - return new LPreviewUtilsImpl(activity); - } else { - return new LPreviewUtilsBase(activity); - } - } -} diff --git a/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtilsImpl.java b/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtilsImpl.java deleted file mode 100644 index d1023d86d3..0000000000 --- a/android/src/lpreview/java/com/google/samples/apps/iosched/util/LPreviewUtilsImpl.java +++ /dev/null @@ -1,206 +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 com.google.samples.apps.iosched.util; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.ActivityOptions; -import android.app.SharedElementListener; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Typeface; -import android.graphics.drawable.AnimatedStateListDrawable; -import android.os.Build; -import android.support.v4.widget.DrawerLayout; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toolbar; - -import com.google.samples.apps.iosched.R; - -import java.lang.Override; -import java.util.List; -import java.util.Map; - -@TargetApi(Build.VERSION_CODES.L) -public class LPreviewUtilsImpl extends LPreviewUtilsBase { - private static final int[] STATE_CHECKED = new int[]{android.R.attr.state_checked}; - private static final int[] STATE_UNCHECKED = new int[]{}; - - private static Typeface sMediumTypeface; - - private ActionBarDrawerToggleWrapper mDrawerToggleWrapper; - private DrawerLayout mDrawerLayout; - private Toolbar mActionBarToolbar; - - LPreviewUtilsImpl(Activity activity) { - super(activity); - } - - @Override - public ActionBarDrawerToggleWrapper setupDrawerToggle(DrawerLayout drawerLayout, DrawerLayout.DrawerListener drawerListener) { - // On L, use a different drawer indicator - if (mActionBarToolbar != null) { - mActionBarToolbar.setNavigationIcon(R.drawable.ic_drawer); - mActionBarToolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mDrawerLayout.isDrawerOpen(Gravity.START)) { - mDrawerLayout.closeDrawer(Gravity.START); - } else { - mDrawerLayout.openDrawer(Gravity.START); - } - } - }); - } else { - mActivity.getActionBar().setHomeAsUpIndicator(R.drawable.ic_drawer); - } - // On L, stub out the ActionBarDrawerToggle - mDrawerLayout = drawerLayout; - mDrawerLayout.setDrawerListener(drawerListener); - mDrawerToggleWrapper = new ActionBarDrawerToggleWrapper(); - return mDrawerToggleWrapper; - } - - public class ActionBarDrawerToggleWrapper extends LPreviewUtilsBase.ActionBarDrawerToggleWrapper { - public void syncState() { - } - - public void onConfigurationChanged(Configuration newConfig) { - } - - public boolean onOptionsItemSelected(MenuItem item) { - // Toggle drawer - if (item.getItemId() == android.R.id.home) { - if (mDrawerLayout.isDrawerOpen(Gravity.START)) { - mDrawerLayout.closeDrawer(Gravity.START); - } else { - mDrawerLayout.openDrawer(Gravity.START); - } - return true; - } - return false; - } - } - - @Override - public void setViewElevation(View v, float elevation) { - v.setElevation(elevation); - } - - @Override - public void trySetActionBar() { - mActionBarToolbar = (Toolbar) mActivity.findViewById(R.id.toolbar_actionbar); - if (mActionBarToolbar != null) { - mActivity.setActionBar(mActionBarToolbar); - } - } - - @Override - public boolean hasLPreviewAPIs() { - return true; - } - - public void startActivityWithTransition(Intent intent, final View clickedView, - final String sharedElementName) { - ActivityOptions options = null; - if (clickedView != null && !TextUtils.isEmpty(sharedElementName)) { - options = ActivityOptions.makeSceneTransitionAnimation( - mActivity, clickedView, sharedElementName); - } - - mActivity.setExitSharedElementListener(new SharedElementListener() { - @Override - public void remapSharedElements(List names, Map sharedElements) { - super.remapSharedElements(names, sharedElements); - sharedElements.put(sharedElementName, clickedView); - } - }); - - mActivity.startActivity(intent, (options != null) ? options.toBundle() : null); - } - - @Override - public void setViewName(View v, String viewName) { - v.setViewName(viewName); - } - - @Override - public void postponeEnterTransition() { - mActivity.postponeEnterTransition(); - } - - @Override - public void startPostponedEnterTransition() { - mActivity.startPostponedEnterTransition(); - } - - @Override - public void showHideActionBarIfPartOfDecor(boolean show) { - if (mActionBarToolbar != null) { - // Action bar is part of the layout - return; - } - - // Action bar is part of window decor - super.showHideActionBarIfPartOfDecor(show); - } - - public boolean shouldChangeActionBarForDrawer() { - return false; - } - - @Override - public void setMediumTypeface(TextView textView) { - if (sMediumTypeface == null) { - sMediumTypeface = Typeface.create("sans-serif-medium", Typeface.NORMAL); - } - - textView.setTypeface(sMediumTypeface); - } - - @Override - public int getStatusBarColor() { - return mActivity.getWindow().getStatusBarColor(); - } - - @Override - public void setStatusBarColor(int color) { - mActivity.getWindow().setStatusBarColor(color); - } - - @Override - public void setOrAnimatePlusCheckIcon(final ImageView imageView, boolean isCheck, - boolean allowAnimate) { - AnimatedStateListDrawable drawable = (AnimatedStateListDrawable) - mActivity.getResources().getDrawable(R.drawable.add_schedule_fab_icon_anim); - imageView.setImageDrawable(drawable); - if (allowAnimate) { - // TODO: figure out if there's a way to always animate from current state - imageView.setImageState(isCheck ? STATE_UNCHECKED : STATE_CHECKED, false); - drawable.jumpToCurrentState(); - imageView.setImageState(isCheck ? STATE_CHECKED : STATE_UNCHECKED, false); - } else { - imageView.setImageState(isCheck ? STATE_CHECKED : STATE_UNCHECKED, false); - drawable.jumpToCurrentState(); - } - } -} diff --git a/android/src/lpreview/res/anim-v21/add_schedule_fab_state_list_anim.xml b/android/src/lpreview/res/anim-v21/add_schedule_fab_state_list_anim.xml deleted file mode 100644 index 4e1d87c594..0000000000 --- a/android/src/lpreview/res/anim-v21/add_schedule_fab_state_list_anim.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/add_schedule_fab_icon_anim.xml b/android/src/lpreview/res/drawable-v21/add_schedule_fab_icon_anim.xml deleted file mode 100644 index 809878eb67..0000000000 --- a/android/src/lpreview/res/drawable-v21/add_schedule_fab_icon_anim.xml +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_off.xml b/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_off.xml deleted file mode 100644 index a8398882a0..0000000000 --- a/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_off.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_on.xml b/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_on.xml deleted file mode 100644 index 3b2dcadc59..0000000000 --- a/android/src/lpreview/res/drawable-v21/add_schedule_fab_ripple_background_on.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/photo_item_foreground.xml b/android/src/lpreview/res/drawable-v21/photo_item_foreground.xml deleted file mode 100644 index a04f8f0870..0000000000 --- a/android/src/lpreview/res/drawable-v21/photo_item_foreground.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/popup_item_background.xml b/android/src/lpreview/res/drawable-v21/popup_item_background.xml deleted file mode 100644 index ffae241719..0000000000 --- a/android/src/lpreview/res/drawable-v21/popup_item_background.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay.xml b/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay.xml deleted file mode 100644 index 43c61ba9e8..0000000000 --- a/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay_dark.xml b/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay_dark.xml deleted file mode 100644 index 77ddeadd21..0000000000 --- a/android/src/lpreview/res/drawable-v21/schedule_item_touchoverlay_dark.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_ab_close.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_ab_close.png deleted file mode 100644 index 319f7af190..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_ab_close.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_000.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_000.png deleted file mode 100644 index 46feef4744..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_000.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_001.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_001.png deleted file mode 100644 index cbbf40cac7..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_001.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_002.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_002.png deleted file mode 100644 index 5deacddbc6..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_002.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_003.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_003.png deleted file mode 100644 index 5abe18a816..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_003.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_004.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_004.png deleted file mode 100644 index ef47e6b19c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_004.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_005.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_005.png deleted file mode 100644 index d77a26d299..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_005.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_006.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_006.png deleted file mode 100644 index 14f724f236..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_006.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_007.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_007.png deleted file mode 100644 index e35e72f9f6..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_007.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_008.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_008.png deleted file mode 100644 index afb842dca9..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_008.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_009.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_009.png deleted file mode 100644 index 5a475ced26..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_009.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_010.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_010.png deleted file mode 100644 index e678436509..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_010.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_011.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_011.png deleted file mode 100644 index 66dab92f6a..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_011.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_012.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_012.png deleted file mode 100644 index 91831387a1..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_012.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_013.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_013.png deleted file mode 100644 index 76efc27871..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_013.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_014.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_014.png deleted file mode 100644 index f625bff336..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_014.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_015.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_015.png deleted file mode 100644 index b3da51ec55..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_015.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_016.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_016.png deleted file mode 100644 index ebfc72e534..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_016.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_017.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_017.png deleted file mode 100644 index ddb0f7fdd4..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_017.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_018.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_018.png deleted file mode 100644 index b870294ee5..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_018.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_019.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_019.png deleted file mode 100644 index 230a339070..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_019.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_020.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_020.png deleted file mode 100644 index a7f351703a..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_020.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_021.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_021.png deleted file mode 100644 index 32020406b8..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_021.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_022.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_022.png deleted file mode 100644 index 4ad8d2eaf3..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_022.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_023.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_023.png deleted file mode 100644 index 62003d1ffa..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_023.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_024.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_024.png deleted file mode 100644 index f6633defc5..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_024.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_025.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_025.png deleted file mode 100644 index 200accf0e3..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_025.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_026.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_026.png deleted file mode 100644 index 00f7159e6c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_026.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_027.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_027.png deleted file mode 100644 index 0950651bc4..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_027.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_028.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_028.png deleted file mode 100644 index abe9fb3bc2..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_028.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_029.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_029.png deleted file mode 100644 index 9c527e8af7..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_029.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_030.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_030.png deleted file mode 100644 index 84d2d4044c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_done_anim_030.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_drawer.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_drawer.png deleted file mode 100644 index 19bae3eed4..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_drawer.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_launcher.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_launcher.png deleted file mode 100644 index 32502c3dd0..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_launcher.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_000.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_000.png deleted file mode 100644 index 84d2d4044c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_000.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_001.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_001.png deleted file mode 100644 index f604272ef8..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_001.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_002.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_002.png deleted file mode 100644 index 99d9b28b7a..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_002.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_003.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_003.png deleted file mode 100644 index cb5d248c8c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_003.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_004.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_004.png deleted file mode 100644 index c297a58dea..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_004.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_005.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_005.png deleted file mode 100644 index 72aace819e..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_005.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_006.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_006.png deleted file mode 100644 index 180d34e110..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_006.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_007.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_007.png deleted file mode 100644 index 581baf2320..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_007.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_008.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_008.png deleted file mode 100644 index 059fbc9236..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_008.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_009.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_009.png deleted file mode 100644 index cb15f09fd3..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_009.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_010.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_010.png deleted file mode 100644 index 369919fd23..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_010.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_011.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_011.png deleted file mode 100644 index 337ec6316e..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_011.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_012.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_012.png deleted file mode 100644 index e9850e40e1..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_012.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_013.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_013.png deleted file mode 100644 index 0e45eba2a7..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_013.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_014.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_014.png deleted file mode 100644 index 6fc754c122..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_014.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_015.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_015.png deleted file mode 100644 index 9bd9e275a1..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_015.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_016.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_016.png deleted file mode 100644 index 08d9dbd1b7..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_016.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_017.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_017.png deleted file mode 100644 index 2d594a8179..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_017.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_018.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_018.png deleted file mode 100644 index abc04376e2..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_018.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_019.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_019.png deleted file mode 100644 index e3f434dde8..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_019.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_020.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_020.png deleted file mode 100644 index ef5d60f3a9..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_020.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_021.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_021.png deleted file mode 100644 index 01e5ebc04a..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_021.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_022.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_022.png deleted file mode 100644 index 4f78187780..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_022.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_023.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_023.png deleted file mode 100644 index 3427a60327..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_023.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_024.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_024.png deleted file mode 100644 index 48cee4affe..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_024.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_025.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_025.png deleted file mode 100644 index 44646927d3..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_025.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_026.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_026.png deleted file mode 100644 index dd43d8174c..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_026.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_027.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_027.png deleted file mode 100644 index 7a5ee16c1b..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_027.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_028.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_028.png deleted file mode 100644 index 5e4863fb75..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_028.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_029.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_029.png deleted file mode 100644 index b03b5cece6..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_029.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_030.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_030.png deleted file mode 100644 index 4372596e05..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_plus_anim_030.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_up.png b/android/src/lpreview/res/drawable-xxhdpi-v21/ic_up.png deleted file mode 100644 index 3396894f86..0000000000 Binary files a/android/src/lpreview/res/drawable-xxhdpi-v21/ic_up.png and /dev/null differ diff --git a/android/src/lpreview/res/drawable-xxxhdpi-v21/ic_launcher.png b/android/src/lpreview/res/drawable-xxxhdpi-v21/ic_launcher.png deleted file mode 100644 index afae355cb0..0000000000 Binary files a/android/src/lpreview/res/drawable-xxxhdpi-v21/ic_launcher.png and /dev/null differ diff --git a/android/src/lpreview/res/layout-v21/include_add_schedule_fab.xml b/android/src/lpreview/res/layout-v21/include_add_schedule_fab.xml deleted file mode 100644 index 591b3aed35..0000000000 --- a/android/src/lpreview/res/layout-v21/include_add_schedule_fab.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - diff --git a/android/src/lpreview/res/layout-v21/toolbar_actionbar.xml b/android/src/lpreview/res/layout-v21/toolbar_actionbar.xml deleted file mode 100644 index 04af7314d6..0000000000 --- a/android/src/lpreview/res/layout-v21/toolbar_actionbar.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_headerbar.xml b/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_headerbar.xml deleted file mode 100644 index 47c3296774..0000000000 --- a/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_headerbar.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_translucent_background.xml b/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_translucent_background.xml deleted file mode 100644 index aca3d8e67a..0000000000 --- a/android/src/lpreview/res/layout-v21/toolbar_actionbar_with_translucent_background.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/android/src/lpreview/res/transition-v21/shared_element.xml b/android/src/lpreview/res/transition-v21/shared_element.xml deleted file mode 100644 index 6d7b28ca45..0000000000 --- a/android/src/lpreview/res/transition-v21/shared_element.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/src/lpreview/res/transition-v21/window_enter_exit.xml b/android/src/lpreview/res/transition-v21/window_enter_exit.xml deleted file mode 100644 index e299a0cb57..0000000000 --- a/android/src/lpreview/res/transition-v21/window_enter_exit.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/android/src/lpreview/res/values-sw600dp-v21/styles.xml b/android/src/lpreview/res/values-sw600dp-v21/styles.xml deleted file mode 100644 index 8165a88b65..0000000000 --- a/android/src/lpreview/res/values-sw600dp-v21/styles.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - diff --git a/android/src/lpreview/res/values-v21/attrs.xml b/android/src/lpreview/res/values-v21/attrs.xml deleted file mode 100644 index 6661e6a4f6..0000000000 --- a/android/src/lpreview/res/values-v21/attrs.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/android/src/lpreview/res/values-v21/dimens.xml b/android/src/lpreview/res/values-v21/dimens.xml deleted file mode 100644 index 6e4a7623ab..0000000000 --- a/android/src/lpreview/res/values-v21/dimens.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - 16dp - diff --git a/android/src/lpreview/res/values-v21/fonts.xml b/android/src/lpreview/res/values-v21/fonts.xml deleted file mode 100644 index 2680a089f0..0000000000 --- a/android/src/lpreview/res/values-v21/fonts.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - 0 - sans-serif-medium - - 0 - sans-serif-medium - diff --git a/android/src/lpreview/res/values-v21/refs.xml b/android/src/lpreview/res/values-v21/refs.xml deleted file mode 100644 index edab140226..0000000000 --- a/android/src/lpreview/res/values-v21/refs.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - @null - diff --git a/android/src/lpreview/res/values-v21/styles.xml b/android/src/lpreview/res/values-v21/styles.xml deleted file mode 100644 index 4d00ccf62f..0000000000 --- a/android/src/lpreview/res/values-v21/styles.xml +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml deleted file mode 100644 index 6c1bd363ed..0000000000 --- a/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/main/assets/licenses.html b/android/src/main/assets/licenses.html deleted file mode 100644 index 451a26940c..0000000000 --- a/android/src/main/assets/licenses.html +++ /dev/null @@ -1,680 +0,0 @@ - - - - -

Notices for files:

-
Copyright 2012 Google, Inc. All rights reserved.
-
-YouTube Android Player API
-
-

Notices for files:

-
SVG Android
-Copyright 2011 Larva Labs LLC and Google, Inc.
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
----
-
-Apache Batik
-Copyright 1999-2007 The Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
-This software contains code from the World Wide Web Consortium (W3C) for the
-Document Object Model API (DOM API) and SVG Document Type Definition (DTD).
-
-This software contains code from the International Organisation for
-Standardization for the definition of character entities used in the software's
-documentation.
-
-This product includes images from the Tango Desktop Project
-(http://tango.freedesktop.org/).
-
-This product includes images from the Pasodoble Icon Theme
-(http://www.jesusda.com/projects/pasodoble).
-
-

Notices for files:

-
Copyright (c) 2012 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.
-
-

Notices for files:

-
Copyright 2012 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.
-
-

Notices for files:

-
/*
- * Copyright 2012 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.
- */
-
-

Notices for files:

-
Google Analytics Android SDK version 2.0 beta 4
-
-Copyright 2009 - 2013 Google, Inc. All rights reserved.
-

Notices for files:

-
Copyright (c) 2013 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.
-
-

Notices for files:

-
Copyright 2013 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.
-

Notices for files:

-
/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-

Notices for files:

-
Copyright 2008-2011 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.
-

Notices for files:

-
Copyright (C) 2010 The Guava Authors
-
-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.
-
-

Notices for files:

-
/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-

Notices for files:

-
Protocol Buffer Java API
-
-BSD License:
-
-Copyright (c) 2007, Google Inc.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-

Notices for files:

-
Copyright (c) 2007-2009, JSR305 expert group
-All rights reserved.
-
-http://www.opensource.org/licenses/bsd-license.php
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-    * Neither the name of the JSR305 expert group nor the names of its
-      contributors may be used to endorse or promote products derived from
-      this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-
-

Notices for files:

-
Copyright 2013 Bump Technologies, Inc. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
-   1. Redistributions of source code must retain the above copyright notice, this list of
-         conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright notice, this list
-         of conditions and the following disclaimer in the documentation and/or other materials
-         provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY BUMP TECHNOLOGIES, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BUMP TECHNOLOGIES, INC. OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-The views and conclusions contained in the software and documentation are those of the
-authors and should not be interpreted as representing official policies, either expressed
-or implied, of Bump Technologies, Inc.
-
-
-Copyright (c) 2013 Xcellent Creations, Inc.
-
-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.
-
-

Notices for files:

-
Copyright (C) 2011 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-

Notices for files:

-
-                                 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.
-
-

Notices for files:

-
-Copyright 2012 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.
-
- diff --git a/android/src/main/assets/maptiles/floor0-2014-v1.svg b/android/src/main/assets/maptiles/floor0-2014-v1.svg deleted file mode 100644 index ca5dc55654..0000000000 --- a/android/src/main/assets/maptiles/floor0-2014-v1.svg +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/main/assets/maptiles/floor1-2014-v1.svg b/android/src/main/assets/maptiles/floor1-2014-v1.svg deleted file mode 100644 index 9cd6c048f8..0000000000 --- a/android/src/main/assets/maptiles/floor1-2014-v1.svg +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/main/assets/maptiles/floor2-2014-v1.svg b/android/src/main/assets/maptiles/floor2-2014-v1.svg deleted file mode 100644 index c1a566d0fb..0000000000 --- a/android/src/main/assets/maptiles/floor2-2014-v1.svg +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/src/main/java/com/google/samples/apps/iosched/Config.java b/android/src/main/java/com/google/samples/apps/iosched/Config.java deleted file mode 100644 index 4151bdd06f..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/Config.java +++ /dev/null @@ -1,246 +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 com.google.samples.apps.iosched; - -import android.net.Uri; - -import com.google.samples.apps.iosched.util.ParserUtils; - -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; - -public class Config { - // General configuration - - // Is this an internal dogfood build? - public static final boolean IS_DOGFOOD_BUILD = false; - - // Warning messages for dogfood build - public static final String DOGFOOD_BUILD_WARNING_TITLE = "Test build"; - public static final String DOGFOOD_BUILD_WARNING_TEXT = "This is a test build."; - - // Public data manifest URL - public static final String PROD_CONFERENCE_DATA_MANIFEST_URL = ""; - - // Manifest URL override for Debug (staging) builds: - public static final String MANIFEST_URL = PROD_CONFERENCE_DATA_MANIFEST_URL; - - public static final String BOOTSTRAP_DATA_TIMESTAMP = "Thu, 10 Apr 2014 00:01:03 GMT"; - - // Conference hashtag - public static final String CONFERENCE_HASHTAG = "#io14"; - - // Patterns that, when absent from a hashtag, will trigger the addition of the - // CONFERENCE_HASHTAG on sharing snippets. Ex: "#Android" will be shared as "#io14 #Android", - // but "#iohunt" won't be modified. - public static final String CONFERENCE_HASHTAG_PREFIX = "#io"; - - // Hard-coded conference dates. This is hardcoded here instead of extracted from the conference - // data to avoid the Schedule UI breaking if some session is incorrectly set to a wrong date. - public static final int CONFERENCE_YEAR = 2014; - - public static final long[][] CONFERENCE_DAYS = new long[][] { - // start and end of day 1 - { ParserUtils.parseTime("2014-06-25T07:00:00.000Z"), - ParserUtils.parseTime("2014-06-26T06:59:59.999Z") }, - // start and end of day 2 - { ParserUtils.parseTime("2014-06-26T07:00:00.000Z"), - ParserUtils.parseTime("2014-06-27T06:59:59.999Z") }, - }; - - public static final TimeZone CONFERENCE_TIMEZONE = TimeZone.getTimeZone("America/Los_Angeles"); - - public static final long CONFERENCE_START_MILLIS = CONFERENCE_DAYS[0][0]; - public static final long CONFERENCE_END_MILLIS = CONFERENCE_DAYS[CONFERENCE_DAYS.length-1][1]; - - // shorthand for some units of time - public static final long SECOND_MILLIS = 1000; - public static final long MINUTE_MILLIS = 60 * SECOND_MILLIS; - public static final long HOUR_MILLIS = 60 * MINUTE_MILLIS; - public static final long DAY_MILLIS = 24 * HOUR_MILLIS; - - // OAuth 2.0 related config - public static final String APP_NAME = "GoogleIO-Android"; - public static final String API_KEY = ""; - - // Announcements - public static final String ANNOUNCEMENTS_PLUS_ID = ""; - - // YouTube API config - public static final String YOUTUBE_API_KEY = ""; - - // YouTube share URL - public static final String YOUTUBE_SHARE_URL_PREFIX = "http://youtu.be/"; - - // Live stream captions config - public static final String LIVESTREAM_CAPTIONS_DARK_THEME_URL_PARAM = "&theme=dark"; - - // Conference public WiFi AP parameters - public static final String WIFI_SSID = "IO2014"; - public static final String WIFI_PASSPHRASE = "letsdothis"; - - // GCM config - public static final String GCM_SERVER_PROD_URL = ""; - public static final String GCM_SERVER_URL = ""; - - // the GCM sender ID is the ID of the app in Google Cloud Console - public static final String GCM_SENDER_ID = ""; - - // The registration api KEY in the gcm server (configured in the GCM - // server's AuthHelper.java file) - public static final String GCM_API_KEY = ""; - - // When do we start to offer to set up the user's wifi? - public static final long WIFI_SETUP_OFFER_START = - CONFERENCE_START_MILLIS - 3 * DAY_MILLIS; // 3 days before conference - - // Format of the youtube link to a Video Library video - public static final String VIDEO_LIBRARY_URL_FMT = "https://www.youtube.com/watch?v=%s"; - - // Fallback URL to get a youtube video thumbnail in case one is not provided in the data - // (normally it should, but this is a safety fallback if it doesn't) - public static final String VIDEO_LIBRARY_FALLBACK_THUMB_URL_FMT = - "http://img.youtube.com/vi/%s/default.jpg"; - - // Link to Google I/O Extended events presented in Explore screen - public static final String IO_EXTENDED_LINK = "http://www.google.com/events/io/io-extended"; - - // 2014-07-25: Time of expiration for experts directory data. - // Represented as elapsed milliseconds since the epoch. - public static final long EXPERTS_DIRECTORY_EXPIRATION = 1406214000000L; - - /** - * Check if the experts directory data expired. - * - * @return True if the experts directory data expired and should be removed. - */ - public static boolean hasExpertsDirectoryExpired() { - return EXPERTS_DIRECTORY_EXPIRATION < System.currentTimeMillis(); - } - - // URL to use for resolving NearbyDevice metadata. - public static final String METADATA_URL = - // "http://url-caster.appspot.com/resolve-scan" - rep("http://example-caster", "example", "url") + "." - + rep("example.com", "example", "appspot") - + rep("/resolve-link", "link", "scan"); - - // How long before a session we display "This session starts in N minutes." in the - // Session details page. - public static final long HINT_TIME_BEFORE_SESSION = 60 * MINUTE_MILLIS; // 60 min - - // how long before the end of a session the user can give feedback - public static final long FEEDBACK_MILLIS_BEFORE_SESSION_END = 15 * MINUTE_MILLIS; // 15min - - // Auto sync interval. Shouldn't be too small, or it might cause battery drain. - public static final long AUTO_SYNC_INTERVAL_LONG_BEFORE_CONFERENCE = 6 * HOUR_MILLIS; - public static final long AUTO_SYNC_INTERVAL_AROUND_CONFERENCE = 2 * HOUR_MILLIS; - public static final long AUTO_SYNC_INTERVAL_AFTER_CONFERENCE = 12 * HOUR_MILLIS; - - // How many days before the conference we consider to be "around the conference date" - // for purposes of sync interval (at which point the AUTO_SYNC_INTERVAL_AROUND_CONFERENCE - // interval kicks in) - public static final long AUTO_SYNC_AROUND_CONFERENCE_THRESH = 3 * DAY_MILLIS; - - // Minimum interval between two consecutive syncs. This is a safety mechanism to throttle - // syncs in case conference data gets updated too often or something else goes wrong that - // causes repeated syncs. - public static final long MIN_INTERVAL_BETWEEN_SYNCS = 10 * MINUTE_MILLIS; - - // If data is not synced in this much time, we show the "data may be stale" warning - public static final long STALE_DATA_THRESHOLD_NOT_DURING_CONFERENCE = 2 * DAY_MILLIS; - public static final long STALE_DATA_THRESHOLD_DURING_CONFERENCE = 12 * HOUR_MILLIS; - - // How long we snooze the stale data notification for after the user has acted on it - // (to keep from showing it repeatedly and being annoying) - public static final long STALE_DATA_WARNING_SNOOZE = 10 * MINUTE_MILLIS; - - // Package name for the I/O Hunt game - public static final String IO_HUNT_PACKAGE_NAME = "com.google.wolff.androidhunt2"; - - // Play store URL prefix - public static final String PLAY_STORE_URL_PREFIX - = "https://play.google.com/store/apps/details?id="; - - // Known session tags that induce special behaviors - public interface Tags { - // tag that indicates a session is a live session - public static final String SESSIONS = "TYPE_SESSIONS"; - - // the tag category that we use to group sessions together when displaying them - public static final String SESSION_GROUPING_TAG_CATEGORY = "TYPE"; - - // tag categories - public static final String CATEGORY_THEME = "THEME"; - public static final String CATEGORY_TOPIC = "TOPIC"; - public static final String CATEGORY_TYPE = "TYPE"; - - public static final Map CATEGORY_DISPLAY_ORDERS - = new HashMap(); - - public static final String SPECIAL_KEYNOTE = "FLAG_KEYNOTE"; - - public static final String[] EXPLORE_CATEGORIES = - { CATEGORY_THEME, CATEGORY_TOPIC, CATEGORY_TYPE }; - - public static final int[] EXPLORE_CATEGORY_ALL_STRING = { - R.string.all_themes, R.string.all_topics, R.string.all_types - }; - - public static final int[] EXPLORE_CATEGORY_TITLE = { - R.string.themes, R.string.topics, R.string.types - }; - } - - static { - Tags.CATEGORY_DISPLAY_ORDERS.put(Tags.CATEGORY_THEME, 0); - Tags.CATEGORY_DISPLAY_ORDERS.put(Tags.CATEGORY_TOPIC, 1); - Tags.CATEGORY_DISPLAY_ORDERS.put(Tags.CATEGORY_TYPE, 2); - } - - // Values for the EventPoint feedback API. Sync happens at the same time as schedule sync, - // and before that values are stored locally in the database. - - public static final String FEEDBACK_API_CODE = ""; - public static final String FEEDBACK_URL = ""; - public static final String FEEDBACK_API_KEY = ""; - public static final String FEEDBACK_DUMMY_REGISTRANT_ID = ""; - public static final String FEEDBACK_SURVEY_ID = ""; - - // URL prefix for web links to session pages - public static final Uri SESSION_DETAIL_WEB_URL_PREFIX - = Uri.parse("https://www.google.com/events/io/schedule/session/"); - - - // Profile URLs for simulated badge reads for the debug feature. - public static final String[] DEBUG_SIMULATED_BADGE_URLS = new String[] {}; - - private static String piece(String s, char start, char end) { - int startIndex = s.indexOf(start), endIndex = s.indexOf(end); - return s.substring(startIndex + 1, endIndex); - } - - private static String piece(String s, char start) { - int startIndex = s.indexOf(start); - return s.substring(startIndex + 1); - } - - private static String rep(String s, String orig, String replacement) { - return s.replaceAll(orig, replacement); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetProvider.java b/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetProvider.java deleted file mode 100644 index 3abc675112..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetProvider.java +++ /dev/null @@ -1,119 +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 com.google.samples.apps.iosched.appwidget; - -import android.accounts.Account; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.widget.RemoteViews; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.sync.SyncHelper; -import com.google.samples.apps.iosched.ui.MyScheduleActivity; -import com.google.samples.apps.iosched.ui.TaskStackBuilderProxyActivity; -import com.google.samples.apps.iosched.util.AccountUtils; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * The app widget's AppWidgetProvider. - */ -public class ScheduleWidgetProvider extends AppWidgetProvider { - private static final String TAG = makeLogTag(ScheduleWidgetProvider.class); - - private static final String REFRESH_ACTION = - "com.google.samples.apps.iosched.appwidget.action.REFRESH"; - private static final String EXTRA_PERFORM_SYNC = - "com.google.samples.apps.iosched.appwidget.extra.PERFORM_SYNC"; - - public static Intent getRefreshBroadcastIntent(Context context, boolean performSync) { - return new Intent(REFRESH_ACTION) - .setComponent(new ComponentName(context, ScheduleWidgetProvider.class)) - .putExtra(EXTRA_PERFORM_SYNC, performSync); - } - - @Override - public void onReceive(final Context context, Intent widgetIntent) { - final String action = widgetIntent.getAction(); - - if (REFRESH_ACTION.equals(action)) { - LOGD(TAG, "received REFRESH_ACTION from widget"); - final boolean shouldSync = widgetIntent.getBooleanExtra(EXTRA_PERFORM_SYNC, false); - - // Trigger sync - Account chosenAccount = AccountUtils.getActiveAccount(context); - if (shouldSync && chosenAccount != null) { - SyncHelper.requestManualSync(chosenAccount); - } - - // Notify the widget that the list view needs to be updated. - final AppWidgetManager mgr = AppWidgetManager.getInstance(context); - final ComponentName cn = new ComponentName(context, ScheduleWidgetProvider.class); - mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), - R.id.widget_schedule_list); - - } - super.onReceive(context, widgetIntent); - } - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - LOGD(TAG, "updating app widget"); - final boolean isAuthenticated = AccountUtils.hasActiveAccount(context); - for (int appWidgetId : appWidgetIds) { - // Specify the service to provide data for the collection widget. Note that we need to - // embed the appWidgetId via the data otherwise it will be ignored. - final Intent intent = new Intent(context, ScheduleWidgetRemoteViewsService.class) - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); - final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget); - rv.setRemoteAdapter(R.id.widget_schedule_list, intent); - - // Set the empty view to be displayed if the collection is empty. It must be a sibling - // view of the collection view. - rv.setEmptyView(R.id.widget_schedule_list, android.R.id.empty); - LOGD(TAG, "setting widget empty view"); - rv.setTextViewText(android.R.id.empty, context.getResources().getString(isAuthenticated - ? R.string.empty_widget_text - : R.string.empty_widget_text_signed_out)); - - final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, - getRefreshBroadcastIntent(context, true), PendingIntent.FLAG_UPDATE_CURRENT); - rv.setOnClickPendingIntent(R.id.widget_refresh_button, refreshPendingIntent); - - final Intent onClickIntent = TaskStackBuilderProxyActivity.getTemplate(context); - final PendingIntent onClickPendingIntent = PendingIntent.getActivity(context, 0, - onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); - rv.setPendingIntentTemplate(R.id.widget_schedule_list, onClickPendingIntent); - - final Intent openAppIntent = new Intent(context, MyScheduleActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final PendingIntent openAppPendingIntent = PendingIntent.getActivity(context, 0, - openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT); - rv.setOnClickPendingIntent(R.id.widget_logo, openAppPendingIntent); - - appWidgetManager.updateAppWidget(appWidgetId, rv); - } - super.onUpdate(context, appWidgetManager, appWidgetIds); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetRemoteViewsService.java b/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetRemoteViewsService.java deleted file mode 100644 index 05c50b1adb..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/appwidget/ScheduleWidgetRemoteViewsService.java +++ /dev/null @@ -1,338 +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 com.google.samples.apps.iosched.appwidget; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Build; -import android.text.format.DateUtils; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.RemoteViewsService; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.model.ScheduleHelper; -import com.google.samples.apps.iosched.model.ScheduleItem; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.ui.MyScheduleActivity; -import com.google.samples.apps.iosched.ui.SimpleSectionedListAdapter; -import com.google.samples.apps.iosched.ui.TaskStackBuilderProxyActivity; -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.TimeUtils; -import com.google.samples.apps.iosched.util.UIUtils; - -import java.util.*; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * This is the service that provides the factory to be bound to the collection service. - */ -public class ScheduleWidgetRemoteViewsService extends RemoteViewsService { - - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) { - return new WidgetRemoteViewsFactory(this.getApplicationContext()); - } - - /** - * This is the factory that will provide data to the collection widget. - */ - private static class WidgetRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { - private static final String TAG = makeLogTag(WidgetRemoteViewsFactory.class); - - private Context mContext; - private SparseIntArray mPMap; - private List mSections; - private SparseBooleanArray mHeaderPositionMap; - - StringBuilder mBuffer = new StringBuilder(); - Formatter mFormatter = new Formatter(mBuffer, Locale.getDefault()); - private ArrayList mScheduleItems; - private int mDefaultSessionColor; - private int mDefaultStartTimeColor; - private int mDefaultEndTimeColor; - - public WidgetRemoteViewsFactory(Context context) { - mContext = context; - } - - public void onCreate() { - // Since we reload the cursor in onDataSetChanged() which gets called immediately after - // onCreate(), we do nothing here. - } - - public void onDestroy() { - - } - - public int getCount() { - if (mScheduleItems == null || !AccountUtils.hasActiveAccount(mContext)) { - return 0; - } - if (mScheduleItems.size() < 10) { - init(); - } - return mScheduleItems.size(); - } - - public int getItemViewType(int position) { - if (position < 0 || position >= mScheduleItems.size()) { - LOGE(TAG, "Invalid view position passed to MyScheduleAdapter: " + position); - return VIEW_TYPE_NORMAL; - } - ScheduleItem item = mScheduleItems.get(position); - long now = UIUtils.getCurrentTime(mContext); - if (item.startTime <= now && now <= item.endTime && item.type == ScheduleItem.SESSION) { - return VIEW_TYPE_NOW; - } else { - return VIEW_TYPE_NORMAL; - } - } - - private static final int VIEW_TYPE_NORMAL = 0; - private static final int VIEW_TYPE_NOW = 1; - - public RemoteViews getViewAt(int position) { - RemoteViews rv; - - boolean isSectionHeader = mHeaderPositionMap.get(position); - int offset = mPMap.get(position); - - if (isSectionHeader) { - rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_schedule_header); - SimpleSectionedListAdapter.Section section = mSections.get(offset - 1); - rv.setTextViewText(R.id.widget_schedule_day, section.getTitle()); - - } else { - int itemPosition = position - offset; - - Intent homeIntent = new Intent(mContext, MyScheduleActivity.class); - - final ScheduleItem item = mScheduleItems.get(itemPosition); - ScheduleItem nextItem = (itemPosition < mScheduleItems.size() - 1) ? mScheduleItems.get(itemPosition + 1) : null; - - if (mDefaultSessionColor < 0) { - mDefaultSessionColor = mContext.getResources().getColor(R.color.default_session_color); - } - - int itemViewType = getItemViewType(itemPosition); - boolean isNowPlaying = false; - boolean isPastDuringConference = false; - int layoutResId = R.layout.widget_schedule_item; - mDefaultStartTimeColor = R.color.body_text_2; - mDefaultEndTimeColor = R.color.body_text_3; - - if (itemViewType == VIEW_TYPE_NOW) { - isNowPlaying = true; - layoutResId = R.layout.widget_schedule_item_now; - mDefaultStartTimeColor = mDefaultEndTimeColor = R.color.body_text_1_inverse; - } else { - if (item.type == ScheduleItem.BREAK) { - layoutResId = R.layout.widget_schedule_item_break; - } - } - - rv = new RemoteViews(mContext.getPackageName(), layoutResId); - - - if (itemPosition < 0 || itemPosition >= mScheduleItems.size()) { - LOGE(TAG, "Invalid view position passed to MyScheduleAdapter: " + position); - return rv; - } - - long now = UIUtils.getCurrentTime(mContext); - boolean showEndTime; - if (item.startTime <= now) { - // session is happening now! - rv.setTextViewText(R.id.start_time, mContext.getString(R.string.session_now)); - showEndTime = nextItem == null || nextItem.startTime != item.endTime; - } else { - // session in the future - rv.setTextViewText(R.id.start_time, TimeUtils.formatShortTime(mContext, new Date(item.startTime))); - // do we need and end time view? - showEndTime = nextItem == null || nextItem.startTime != item.endTime; - } - - if (showEndTime) { - rv.setViewVisibility(R.id.end_time, View.VISIBLE); - rv.setTextViewText(R.id.end_time, mContext.getString(R.string.schedule_end_time, - TimeUtils.formatShortTime(mContext, new Date(item.endTime)))); - } else { - // no need to show end time - rv.setViewVisibility(R.id.end_time, View.GONE); - } - - - rv.setViewVisibility(R.id.live_now_badge, View.GONE); - - rv.setViewVisibility(R.id.conflict_warning, View.GONE); - - // Set default colors to time indicators, in case they were overridden by conflict warning: - if (!isNowPlaying) { - rv.setTextColor(R.id.start_time, mContext.getResources().getColor(mDefaultStartTimeColor)); - rv.setTextColor(R.id.end_time, mContext.getResources().getColor(mDefaultEndTimeColor)); - - } - - if (item.type == ScheduleItem.FREE) { - - rv.setImageViewResource(R.id.background_image, R.drawable.schedule_item_free); - - rv.setTextViewText(R.id.slot_title, mContext.getText(R.string.browse_sessions)); - rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.theme_primary)); - - rv.setTextViewText(R.id.slot_subtitle, item.subtitle); - rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2)); - - Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( - homeIntent, - new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions.buildUnscheduledSessionsInInterval( - item.startTime, item.endTime)) - ); - rv.setOnClickFillInIntent(R.id.box, fillIntent); - - } else if (item.type == ScheduleItem.BREAK) { - rv.setImageViewResource(R.id.background_image, R.drawable.schedule_item_break); - - rv.setTextViewText(R.id.slot_title, item.title); - rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.body_text_1)); - - rv.setTextViewText(R.id.slot_subtitle, item.subtitle); - rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2)); - - } else if (item.type == ScheduleItem.SESSION) { - final int color = UIUtils.scaleSessionColorToDefaultBG( - item.backgroundColor == 0 ? mDefaultSessionColor : item.backgroundColor); - - Bitmap image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - image.eraseColor(color); - rv.setImageViewBitmap(R.id.background_image, image); - - rv.setTextViewText(R.id.slot_title, item.title); - rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.body_text_1_inverse)); - - rv.setTextViewText(R.id.slot_subtitle, item.subtitle); - rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2_inverse)); - - // show or hide the "LIVE NOW" badge - final boolean showLiveBadge = 0 != (item.flags & ScheduleItem.FLAG_HAS_LIVESTREAM) - && now >= item.startTime && now <= item.endTime; - rv.setViewVisibility(R.id.live_now_badge, (showLiveBadge ? View.VISIBLE : View.GONE)); - - // show or hide the "conflict" warning - if (!isPastDuringConference) { - final boolean showConflict = 0 != (item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_PREVIOUS); - rv.setViewVisibility(R.id.conflict_warning, (showConflict ? View.VISIBLE : View.GONE)); - if (showConflict && !isNowPlaying) { - int conflictColor = mContext.getResources().getColor(R.color.my_schedule_conflict); - rv.setTextColor(R.id.start_time, conflictColor); - rv.setTextColor(R.id.end_time, conflictColor); - } - } - - Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( - homeIntent, - new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions.buildSessionUri(item.sessionId))); - rv.setOnClickFillInIntent(R.id.box, fillIntent); - - } else { - LOGE(TAG, "Invalid item type in MyScheduleAdapter: " + item.type); - } - } - - return rv; - } - - @Override - public RemoteViews getLoadingView() { - return null; - } - - @Override - public int getViewTypeCount() { - return 4; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public void onDataSetChanged() { - init(); - } - - private void init() { - ScheduleHelper scheduleHelper = new ScheduleHelper(mContext); - - //Fetch all sessions and blocks - List allScheduleItems = scheduleHelper.getScheduleData(Long.MIN_VALUE, Long.MAX_VALUE); - - String displayTimeZone = PrefUtils.getDisplayTimeZone(mContext).getID(); - - mSections = new ArrayList(); - long previousTime = -1; - long time; - mPMap = new SparseIntArray(); - mHeaderPositionMap = new SparseBooleanArray(); - int offset = 0; - int globalPosition = 0; - int position = 0; - mScheduleItems = new ArrayList(); - for (ScheduleItem item : allScheduleItems) { - if (item.endTime <= UIUtils.getCurrentTime(mContext)) { - continue; - } - mScheduleItems.add(item); - time = item.startTime; - if (!UIUtils.isSameDayDisplay(previousTime, time, mContext)) { - mBuffer.setLength(0); - mSections.add(new SimpleSectionedListAdapter.Section(position, - DateUtils.formatDateRange( - mContext, mFormatter, - time, time, - DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE, - displayTimeZone - ).toString() - )); - ++offset; - mHeaderPositionMap.put(globalPosition, true); - mPMap.put(globalPosition, offset); - ++globalPosition; - } - mHeaderPositionMap.put(globalPosition, false); - mPMap.put(globalPosition, offset); - ++globalPosition; - ++position; - previousTime = time; - } - - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMCommand.java deleted file mode 100644 index 40e0c1cf99..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMCommand.java +++ /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. - */ -package com.google.samples.apps.iosched.gcm; - -import android.content.Context; - -public abstract class GCMCommand { - public abstract void execute(Context context, String type, String extraData); -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMIntentService.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMIntentService.java deleted file mode 100644 index 892f6e3537..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMIntentService.java +++ /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. - */ -package com.google.samples.apps.iosched.gcm; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.gcm.command.*; -import com.google.android.gcm.GCMBaseIntentService; -import com.google.samples.apps.iosched.util.AccountUtils; - -import android.content.Context; -import android.content.Intent; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.LOGE; -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.LOGW; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * {@link android.app.IntentService} responsible for handling GCM messages. - */ -public class GCMIntentService extends GCMBaseIntentService { - - private static final String TAG = makeLogTag("GCM"); - - private static final Map MESSAGE_RECEIVERS; - static { - // Known messages and their GCM message receivers - Map receivers = new HashMap(); - receivers.put("test", new TestCommand()); - receivers.put("announcement", new AnnouncementCommand()); - receivers.put("sync_schedule", new SyncCommand()); - receivers.put("sync_user", new SyncUserCommand()); - receivers.put("notification", new NotificationCommand()); - MESSAGE_RECEIVERS = Collections.unmodifiableMap(receivers); - } - - public GCMIntentService() { - super(Config.GCM_SENDER_ID); - } - - @Override - protected void onRegistered(Context context, String regId) { - LOGI(TAG, "Device registered: regId=" + regId); - ServerUtilities.register(context, regId, AccountUtils.getPlusProfileId(this)); - } - - @Override - protected void onUnregistered(Context context, String regId) { - LOGI(TAG, "Device unregistered"); - if (ServerUtilities.isRegisteredOnServer(context, AccountUtils.getPlusProfileId(this))) { - ServerUtilities.unregister(context, regId); - } else { - // This callback results from the call to unregister made on - // ServerUtilities when the registration to the server failed. - LOGD(TAG, "Ignoring unregister callback"); - } - } - - @Override - protected void onMessage(Context context, Intent intent) { - String action = intent.getStringExtra("action"); - String extraData = intent.getStringExtra("extraData"); - LOGD(TAG, "Got GCM message, action=" + action + ", extraData=" + extraData); - - if (action == null) { - LOGE(TAG, "Message received without command action"); - return; - } - - action = action.toLowerCase(); - GCMCommand command = MESSAGE_RECEIVERS.get(action); - if (command == null) { - LOGE(TAG, "Unknown command received: " + action); - } else { - command.execute(this, action, extraData); - } - - } - - @Override - public void onError(Context context, String errorId) { - LOGE(TAG, "Received error: " + errorId); - } - - @Override - protected boolean onRecoverableError(Context context, String errorId) { - // log message - LOGW(TAG, "Received recoverable error: " + errorId); - return super.onRecoverableError(context, errorId); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMRedirectedBroadcastReceiver.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMRedirectedBroadcastReceiver.java deleted file mode 100644 index f928e6d63d..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/GCMRedirectedBroadcastReceiver.java +++ /dev/null @@ -1,40 +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 com.google.samples.apps.iosched.gcm; - -import com.google.android.gcm.GCMBroadcastReceiver; - -import android.content.Context; - -/** - * @author trevorjohns@google.com (Trevor Johns) - */ -public class GCMRedirectedBroadcastReceiver extends GCMBroadcastReceiver { - - /** - * Gets the class name of the intent service that will handle GCM messages. - * - * Used to override class name, so that GCMIntentService can live in a - * subpackage. - */ - @Override - protected String getGCMIntentServiceClassName(Context context) { - return GCMIntentService.class.getCanonicalName(); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/ServerUtilities.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/ServerUtilities.java deleted file mode 100644 index e85b74d30a..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/ServerUtilities.java +++ /dev/null @@ -1,303 +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 com.google.samples.apps.iosched.gcm; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.util.AccountUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; -import java.util.Map.Entry; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Helper class used to communicate with the demo server. - */ -public final class ServerUtilities { - private static final String TAG = makeLogTag("GCMs"); - - private static final String PREFERENCES = "com.google.samples.apps.iosched.gcm"; - private static final String PROPERTY_REGISTERED_TS = "registered_ts"; - private static final String PROPERTY_REG_ID = "reg_id"; - private static final String PROPERTY_GCM_KEY = "gcm_key"; - private static final int MAX_ATTEMPTS = 5; - private static final int BACKOFF_MILLI_SECONDS = 2000; - - private static final Random sRandom = new Random(); - - private static boolean checkGcmEnabled() { - if (TextUtils.isEmpty(Config.GCM_SERVER_URL)) { - LOGD(TAG, "GCM feature disabled (no URL configured)"); - return false; - } else if (TextUtils.isEmpty(Config.GCM_API_KEY)) { - LOGD(TAG, "GCM feature disabled (no API key configured)"); - return false; - } else if (TextUtils.isEmpty(Config.GCM_SENDER_ID)) { - LOGD(TAG, "GCM feature disabled (no sender ID configured)"); - return false; - } - return true; - } - - /** - * Register this account/device pair within the server. - * - * @param context Current context - * @param gcmId The GCM registration ID for this device - * @param gcmKey The GCM key with which to register. - * @return whether the registration succeeded or not. - */ - public static boolean register(final Context context, final String gcmId, final String gcmKey) { - if (!checkGcmEnabled()) { - return false; - } - - LOGI(TAG, "registering device (gcm_id = " + gcmId + ")"); - String serverUrl = Config.GCM_SERVER_URL + "/register"; - LOGI(TAG, "registering on GCM with GCM key: " + AccountUtils.sanitizeGcmKey(gcmKey)); - - Map params = new HashMap(); - params.put("gcm_id", gcmId); - params.put("gcm_key", gcmKey); - long backoff = BACKOFF_MILLI_SECONDS + sRandom.nextInt(1000); - // Once GCM returns a registration id, we need to register it in the - // demo server. As the server might be down, we will retry it a couple - // times. - for (int i = 1; i <= MAX_ATTEMPTS; i++) { - LOGV(TAG, "Attempt #" + i + " to register"); - try { - post(serverUrl, params, Config.GCM_API_KEY); - setRegisteredOnServer(context, true, gcmId, gcmKey); - return true; - } catch (IOException e) { - // Here we are simplifying and retrying on any error; in a real - // application, it should retry only on unrecoverable errors - // (like HTTP error code 503). - LOGE(TAG, "Failed to register on attempt " + i, e); - if (i == MAX_ATTEMPTS) { - break; - } - try { - LOGV(TAG, "Sleeping for " + backoff + " ms before retry"); - Thread.sleep(backoff); - } catch (InterruptedException e1) { - // Activity finished before we complete - exit. - LOGD(TAG, "Thread interrupted: abort remaining retries!"); - Thread.currentThread().interrupt(); - return false; - } - // increase backoff exponentially - backoff *= 2; - } - } - return false; - } - - /** - * Unregister this account/device pair within the server. - * - * @param context Current context - * @param gcmId The GCM registration ID for this device - */ - static void unregister(final Context context, final String gcmId) { - if (!checkGcmEnabled()) { - return; - } - - LOGI(TAG, "unregistering device (gcmId = " + gcmId + ")"); - String serverUrl = Config.GCM_SERVER_URL + "/unregister"; - Map params = new HashMap(); - params.put("gcm_id", gcmId); - try { - post(serverUrl, params, Config.GCM_API_KEY); - setRegisteredOnServer(context, false, gcmId, null); - } catch (IOException e) { - // At this point the device is unregistered from GCM, but still - // registered in the server. - // We could try to unregister again, but it is not necessary: - // if the server tries to send a message to the device, it will get - // a "NotRegistered" error message and should unregister the device. - LOGD(TAG, "Unable to unregister from application server", e); - } finally { - // Regardless of server success, clear local preferences - setRegisteredOnServer(context, false, null, null); - } - } - - /** - * Request user data sync. - * - * @param context Current context - */ - public static void notifyUserDataChanged(final Context context) { - if (!checkGcmEnabled()) { - return; - } - - LOGI(TAG, "Notifying GCM that user data changed"); - String serverUrl = Config.GCM_SERVER_URL + "/send/self/sync_user"; - try { - String gcmKey = AccountUtils.getGcmKey(context, AccountUtils.getActiveAccountName(context)); - if (gcmKey != null) { - post(serverUrl, new HashMap(), gcmKey); - } - } catch (IOException e) { - LOGE(TAG, "Unable to notify GCM about user data change", e); - } - } - - /** - * Sets whether the device was successfully registered in the server side. - * - * @param context Current context - * @param flag True if registration was successful, false otherwise - * @param gcmId True if registration was successful, false otherwise - */ - private static void setRegisteredOnServer(Context context, boolean flag, String gcmId, String gcmKey) { - final SharedPreferences prefs = context.getSharedPreferences( - PREFERENCES, Context.MODE_PRIVATE); - LOGD(TAG, "Setting registered on server status as: " + flag + ", gcmKey=" - + AccountUtils.sanitizeGcmKey(gcmKey)); - Editor editor = prefs.edit(); - if (flag) { - editor.putLong(PROPERTY_REGISTERED_TS, new Date().getTime()); - editor.putString(PROPERTY_GCM_KEY, gcmKey == null ? "" : gcmKey); - editor.putString(PROPERTY_REG_ID, gcmId); - } else { - editor.remove(PROPERTY_REG_ID); - } - editor.commit(); - } - - /** - * Checks whether the device was successfully registered in the server side. - * - * @param context Current context - * @return True if registration was successful, false otherwise - */ - public static boolean isRegisteredOnServer(Context context, String gcmKey) { - final SharedPreferences prefs = context.getSharedPreferences( - PREFERENCES, Context.MODE_PRIVATE); - // Find registration threshold - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.DATE, -1); - long yesterdayTS = cal.getTimeInMillis(); - long regTS = prefs.getLong(PROPERTY_REGISTERED_TS, 0); - - gcmKey = gcmKey == null ? "" : gcmKey; - - if (regTS > yesterdayTS) { - LOGV(TAG, "GCM registration current. regTS=" + regTS + " yesterdayTS=" + yesterdayTS); - - final String registeredGcmKey = prefs.getString(PROPERTY_GCM_KEY, ""); - if (registeredGcmKey.equals(gcmKey)) { - LOGD(TAG, "GCM registration is valid and for the correct gcm key: " - + AccountUtils.sanitizeGcmKey(registeredGcmKey)); - return true; - } - LOGD(TAG, "GCM registration is for DIFFERENT gcm key " - + AccountUtils.sanitizeGcmKey(registeredGcmKey) + ". We were expecting " - + AccountUtils.sanitizeGcmKey(gcmKey)); - return false; - } else { - LOGV(TAG, "GCM registration expired. regTS=" + regTS + " yesterdayTS=" + yesterdayTS); - return false; - } - } - - public static String getGcmId(Context context) { - final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); - return prefs.getString(PROPERTY_REG_ID, null); - } - - /** - * Unregister the current GCM ID when we sign-out - * - * @param context Current context - */ - public static void onSignOut(Context context) { - String gcmId = getGcmId(context); - if (gcmId != null) { - unregister(context, gcmId); - } - } - - /** - * Issue a POST request to the server. - * - * @param endpoint POST address. - * @param params request parameters. - * @throws java.io.IOException propagated from POST. - */ - private static void post(String endpoint, Map params, String key) - throws IOException { - URL url; - try { - url = new URL(endpoint); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("invalid url: " + endpoint); - } - params.put("key", key); - StringBuilder bodyBuilder = new StringBuilder(); - Iterator> iterator = params.entrySet().iterator(); - // constructs the POST body using the parameters - while (iterator.hasNext()) { - Entry param = iterator.next(); - bodyBuilder.append(param.getKey()).append('=') - .append(param.getValue()); - if (iterator.hasNext()) { - bodyBuilder.append('&'); - } - } - String body = bodyBuilder.toString(); - LOGV(TAG, "Posting '" + body + "' to " + url); - HttpURLConnection conn = null; - try { - conn = (HttpURLConnection) url.openConnection(); - conn.setDoOutput(true); - conn.setUseCaches(false); - conn.setChunkedStreamingMode(0); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", - "application/x-www-form-urlencoded;charset=UTF-8"); - conn.setRequestProperty("Content-Length", - Integer.toString(body.length())); - // post the request - OutputStream out = conn.getOutputStream(); - out.write(body.getBytes()); - out.close(); - // handle the response - int status = conn.getResponseCode(); - if (status != 200) { - throw new IOException("Post failed with error code " + status); - } - } finally { - if (conn != null) { - conn.disconnect(); - } - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/AnnouncementCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/command/AnnouncementCommand.java deleted file mode 100644 index 1557f08a83..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/AnnouncementCommand.java +++ /dev/null @@ -1,63 +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 com.google.samples.apps.iosched.gcm.command; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.support.v4.app.NotificationCompat; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.gcm.GCMCommand; -import com.google.samples.apps.iosched.ui.MyScheduleActivity; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class AnnouncementCommand extends GCMCommand { - private static final String TAG = makeLogTag("AnnouncementCommand"); - - @Override - public void execute(Context context, String type, String extraData) { - LOGI(TAG, "Received GCM message: " + type); - displayNotification(context, extraData); - } - - private void displayNotification(Context context, String message) { - LOGI(TAG, "Displaying notification: " + message); - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(0, new NotificationCompat.Builder(context) - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_stat_notification) - .setTicker(message) - .setContentTitle(context.getString(R.string.app_name)) - .setContentText(message) - //.setColor(context.getResources().getColor(R.color.theme_primary)) - // Note: setColor() is available in the support lib v21+. - // We commented it out because we want the source to compile - // against support lib v20. If you are using support lib - // v21 or above on Android L, uncomment this line. - .setContentIntent( - PendingIntent.getActivity(context, 0, - new Intent(context, MyScheduleActivity.class) - .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_SINGLE_TOP), - 0)) - .setAutoCancel(true) - .build()); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/NotificationCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/command/NotificationCommand.java deleted file mode 100644 index 03ec5099f1..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/NotificationCommand.java +++ /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. - */ -package com.google.samples.apps.iosched.gcm.command; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.support.v4.app.NotificationCompat; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.gcm.GCMCommand; -import com.google.samples.apps.iosched.ui.MyScheduleActivity; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.TimeUtils; -import com.google.samples.apps.iosched.util.UIUtils; -import com.google.gson.Gson; - -import java.util.Date; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class NotificationCommand extends GCMCommand { - private static final String TAG = makeLogTag("NotificationCommand"); - - private static class NotificationCommandModel { - String format; - String audience; - String minVersion; - String maxVersion; - String title; - String message; - String expiry; - String dialogTitle; - String dialogText; - String dialogYes; - String dialogNo; - String url; - } - - @Override - public void execute(Context context, String type, String payload) { - LOGI(TAG, "Received GCM message: " + type); - LOGI(TAG, "Parsing GCM notification command: " + payload); - Gson gson = new Gson(); - NotificationCommandModel command; - try { - command = gson.fromJson(payload, NotificationCommandModel.class); - if (command == null) { - LOGE(TAG, "Failed to parse command (gson returned null)."); - return; - } - LOGD(TAG, "Format: " + command.format); - LOGD(TAG, "Audience: " + command.audience); - LOGD(TAG, "Title: " + command.title); - LOGD(TAG, "Message: " + command.message); - LOGD(TAG, "Expiry: " + command.expiry); - LOGD(TAG, "URL: " + command.url); - LOGD(TAG, "Dialog title: " + command.dialogTitle); - LOGD(TAG, "Dialog text: " + command.dialogText); - LOGD(TAG, "Dialog yes: " + command.dialogYes); - LOGD(TAG, "Dialog no: " + command.dialogNo); - LOGD(TAG, "Min version code: " + command.minVersion); - LOGD(TAG, "Max version code: " + command.maxVersion); - } catch (Exception ex) { - ex.printStackTrace(); - LOGE(TAG, "Failed to parse GCM notification command."); - return; - } - - LOGD(TAG, "Processing notification command."); - processCommand(context, command); - } - - private void processCommand(Context context, NotificationCommandModel command) { - // Check format - if (!"1.0.00".equals(command.format)) { - LOGW(TAG, "GCM notification command has unrecognized format: " + command.format); - return; - } - - // Check app version - if (!TextUtils.isEmpty(command.minVersion) || !TextUtils.isEmpty(command.maxVersion)) { - LOGD(TAG, "Command has version range."); - int minVersion = 0; - int maxVersion = Integer.MAX_VALUE; - try { - if (!TextUtils.isEmpty(command.minVersion)) { - minVersion = Integer.parseInt(command.minVersion); - } - if (!TextUtils.isEmpty(command.maxVersion)) { - maxVersion = Integer.parseInt(command.maxVersion); - } - LOGD(TAG, "Version range: " + minVersion + " - " + maxVersion); - PackageInfo pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - LOGD(TAG, "My version code: " + pinfo.versionCode); - if (pinfo.versionCode < minVersion) { - LOGD(TAG, "Skipping command because our version is too old, " - + pinfo.versionCode + " < " + minVersion); - return; - } - if (pinfo.versionCode > maxVersion) { - LOGD(TAG, "Skipping command because our version is too new, " - + pinfo.versionCode + " > " + maxVersion); - return; - } - } catch (NumberFormatException ex) { - LOGE(TAG, "Version spec badly formatted: min=" + command.minVersion - + ", max=" + command.maxVersion); - return; - } catch (Exception ex) { - LOGE(TAG, "Unexpected problem doing version check.", ex); - return; - } - } - - // Check if we are the right audience - LOGD(TAG, "Checking audience: " + command.audience); - if ("remote".equals(command.audience)) { - if (PrefUtils.isAttendeeAtVenue(context)) { - LOGD(TAG, "Ignoring notification because audience is remote and attendee is on-site"); - return; - } else { - LOGD(TAG, "Relevant (attendee is remote)."); - } - } else if ("local".equals(command.audience)) { - if (!PrefUtils.isAttendeeAtVenue(context)) { - LOGD(TAG, "Ignoring notification because audience is on-site and attendee is remote."); - return; - } else { - LOGD(TAG, "Relevant (attendee is local)."); - } - } else if ("all".equals(command.audience)) { - LOGD(TAG, "Relevant (audience is 'all')."); - } else { - LOGE(TAG, "Invalid audience on GCM notification command: " + command.audience); - return; - } - - // Check if it expired - Date expiry = command.expiry == null ? null : TimeUtils.parseTimestamp(command.expiry); - if (expiry == null) { - LOGW(TAG, "Failed to parse expiry field of GCM notification command: " + command.expiry); - return; - } else if (expiry.getTime() < UIUtils.getCurrentTime(context)) { - LOGW(TAG, "Got expired GCM notification command. Expiry: " + expiry.toString()); - return; - } else { - LOGD(TAG, "Message is still valid (expiry is in the future: " + expiry.toString() + ")"); - } - - // decide the intent that will be fired when the user clicks the notification - Intent intent; - if (TextUtils.isEmpty(command.dialogText)) { - // notification leads directly to the URL, no dialog - if (TextUtils.isEmpty(command.url)) { - intent = new Intent(context, MyScheduleActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_SINGLE_TOP); - } else { - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(command.url)); - } - } else { - // use a dialog - intent = new Intent(context, MyScheduleActivity.class).setFlags( - Intent.FLAG_ACTIVITY_CLEAR_TOP | - Intent.FLAG_ACTIVITY_SINGLE_TOP | - Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.putExtra(MyScheduleActivity.EXTRA_DIALOG_TITLE, - command.dialogTitle == null ? "" : command.dialogTitle); - intent.putExtra(MyScheduleActivity.EXTRA_DIALOG_MESSAGE, - command.dialogText == null ? "" : command.dialogText); - intent.putExtra(MyScheduleActivity.EXTRA_DIALOG_YES, - command.dialogYes == null ? "OK" : command.dialogYes); - intent.putExtra(MyScheduleActivity.EXTRA_DIALOG_NO, - command.dialogNo == null ? "" : command.dialogNo); - intent.putExtra(MyScheduleActivity.EXTRA_DIALOG_URL, - command.url == null ? "" : command.url); - } - - final String title = TextUtils.isEmpty(command.title) ? - context.getString(R.string.app_name) : command.title; - final String message = TextUtils.isEmpty(command.message) ? "" : command.message; - - // fire the notification - ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)) - .notify(0, new NotificationCompat.Builder(context) - .setWhen(System.currentTimeMillis()) - .setSmallIcon(R.drawable.ic_stat_notification) - .setTicker(command.message) - .setContentTitle(title) - .setContentText(message) - //.setColor(context.getResources().getColor(R.color.theme_primary)) - // Note: setColor() is available in the support lib v21+. - // We commented it out because we want the source to compile - // against support lib v20. If you are using support lib - // v21 or above on Android L, uncomment this line. - .setContentIntent(PendingIntent.getActivity(context, 0, intent, - PendingIntent.FLAG_CANCEL_CURRENT)) - .setAutoCancel(true) - .build()); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncCommand.java deleted file mode 100644 index a8e4d5baa6..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncCommand.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. - */ -package com.google.samples.apps.iosched.gcm.command; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import com.google.samples.apps.iosched.gcm.GCMCommand; -import com.google.samples.apps.iosched.sync.TriggerSyncReceiver; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -import java.util.Random; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class SyncCommand extends GCMCommand { - private static final String TAG = makeLogTag("SyncCommand"); - private static final int DEFAULT_TRIGGER_SYNC_MAX_JITTER_MILLIS = 15 * 60 * 1000; // 15 minutes - private static final Random RANDOM = new Random(); - - @Override - public void execute(Context context, String type, String extraData) { - LOGI(TAG, "Received GCM message: " + type); - int syncJitter; - SyncData syncData = null; - if (extraData != null) { - try { - Gson gson = new Gson(); - syncData = gson.fromJson(extraData, SyncData.class); - } catch (JsonSyntaxException e) { - LOGI(TAG, "Error while decoding extraData: " + e.toString()); - } - } - - if (syncData != null && syncData.sync_jitter != 0) { - syncJitter = syncData.sync_jitter; - } else { - syncJitter = DEFAULT_TRIGGER_SYNC_MAX_JITTER_MILLIS; - } - - scheduleSync(context, syncJitter); - } - - private void scheduleSync(Context context, int syncJitter) { - int jitterMillis = (int) (RANDOM.nextFloat() * syncJitter); - final String debugMessage = "Scheduling next sync for " + jitterMillis + "ms"; - LOGI(TAG, debugMessage); - - ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)) - .set( - AlarmManager.RTC, - System.currentTimeMillis() + jitterMillis, - PendingIntent.getBroadcast( - context, - 0, - new Intent(context, TriggerSyncReceiver.class), - PendingIntent.FLAG_CANCEL_CURRENT)); - - } - - class SyncData { - private int sync_jitter; - SyncData() {} - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncUserCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncUserCommand.java deleted file mode 100644 index 78f91dd6b0..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/SyncUserCommand.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 com.google.samples.apps.iosched.gcm.command; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.google.samples.apps.iosched.gcm.GCMCommand; -import com.google.samples.apps.iosched.sync.TriggerSyncReceiver; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class SyncUserCommand extends GCMCommand { - private static final String TAG = makeLogTag("TestCommand"); - private static final int DEFAULT_TRIGGER_SYNC_DELAY = 0; // default to immediately sync - - @Override - public void execute(Context context, String type, String extraData) { - LOGI(TAG, "Received GCM message: " + type); - int syncJitter; - SyncData syncData = null; - if (extraData != null) { - try { - Gson gson = new Gson(); - syncData = gson.fromJson(extraData, SyncData.class); - } catch (JsonSyntaxException e) { - LOGI(TAG, "Error while decoding extraData: " + e.toString()); - } - } - - // TODO: Also, use superclass to reduce duplcated code between this and SyncCommand - if (syncData != null && syncData.sync_jitter != 0) { - syncJitter = syncData.sync_jitter; - } else { - syncJitter = DEFAULT_TRIGGER_SYNC_DELAY; - } - - scheduleSync(context, syncJitter); - } - - private void scheduleSync(Context context, int syncDelay) { - // Use delay instead of jitter, since we're trying to squelch messages - int jitterMillis = syncDelay; - - final String debugMessage = "Scheduling next user data sync for " + jitterMillis + "ms"; - LOGI(TAG, debugMessage); - - final Intent intent = new Intent(context, TriggerSyncReceiver.class); - intent.putExtra(TriggerSyncReceiver.EXTRA_USER_DATA_SYNC_ONLY, true); - ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)) - .set( - AlarmManager.RTC, - System.currentTimeMillis() + jitterMillis, - PendingIntent.getBroadcast( - context, - 0, - intent, - PendingIntent.FLAG_CANCEL_CURRENT) - ); - - } - - class SyncData { - private int sync_jitter; - SyncData() {} - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/TestCommand.java b/android/src/main/java/com/google/samples/apps/iosched/gcm/command/TestCommand.java deleted file mode 100644 index 8dfd8baf00..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/gcm/command/TestCommand.java +++ /dev/null @@ -1,31 +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 com.google.samples.apps.iosched.gcm.command; - -import android.content.Context; -import com.google.samples.apps.iosched.gcm.GCMCommand; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class TestCommand extends GCMCommand { - private static final String TAG = makeLogTag("TestCommand"); - - @Override - public void execute(Context context, String type, String extraData) { - LOGI(TAG, "Received GCM message: type=" + type + ", extraData=" + extraData); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/AnnouncementsFetcher.java b/android/src/main/java/com/google/samples/apps/iosched/io/AnnouncementsFetcher.java deleted file mode 100644 index 02d1bd8653..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/AnnouncementsFetcher.java +++ /dev/null @@ -1,123 +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 com.google.samples.apps.iosched.io; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.provider.ScheduleContract.Announcements; -import com.google.samples.apps.iosched.provider.ScheduleContract.SyncColumns; -import com.google.samples.apps.iosched.util.Lists; -import com.google.samples.apps.iosched.util.NetUtils; -import com.google.api.client.extensions.android.json.AndroidJsonFactory; -import com.google.api.client.googleapis.services.CommonGoogleClientRequestInitializer; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.services.plus.Plus; -import com.google.api.services.plus.model.Activity; -import com.google.api.services.plus.model.ActivityFeed; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.text.TextUtils; - -import java.io.IOException; -import java.util.ArrayList; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGE; -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class AnnouncementsFetcher { - private static final String TAG = makeLogTag(AnnouncementsFetcher.class); - private Context mContext; - - public AnnouncementsFetcher(Context context) { - mContext = context; - } - - public ArrayList fetchAndParse() throws IOException { - final ArrayList batch = Lists.newArrayList(); - - // Set up the HTTP transport and JSON factory - HttpTransport httpTransport = new NetHttpTransport(); - JsonFactory jsonFactory = new AndroidJsonFactory(); - - // Set up the main Google+ class - Plus plus = new Plus.Builder(httpTransport, jsonFactory, null) - .setApplicationName(NetUtils.getUserAgent(mContext)) - .setGoogleClientRequestInitializer( - new CommonGoogleClientRequestInitializer(Config.API_KEY)) - .build(); - - ActivityFeed activities; - try { - activities = plus.activities().list(Config.ANNOUNCEMENTS_PLUS_ID, "public") - .setMaxResults(100l) - .execute(); - if (activities == null || activities.getItems() == null) { - throw new IOException("Activities list was null."); - } - - } catch (IOException e) { - LOGE(TAG, "Error fetching announcements", e); - return batch; - } - - LOGI(TAG, "Updating announcements data"); - - // Clear out existing announcements - batch.add(ContentProviderOperation - .newDelete(ScheduleContract.addCallerIsSyncAdapterParameter( - Announcements.CONTENT_URI)) - .build()); - - StringBuilder sb = new StringBuilder(); - for (Activity activity : activities.getItems()) { - // Filter out anything not including the conference hashtag. - sb.setLength(0); - appendIfNotEmpty(sb, activity.getAnnotation()); - if (activity.getObject() != null) { - appendIfNotEmpty(sb, activity.getObject().getContent()); - } - - if (!sb.toString().contains(Config.CONFERENCE_HASHTAG)) { - continue; - } - - // Insert announcement info - batch.add(ContentProviderOperation - .newInsert(ScheduleContract - .addCallerIsSyncAdapterParameter(Announcements.CONTENT_URI)) - .withValue(SyncColumns.UPDATED, System.currentTimeMillis()) - .withValue(Announcements.ANNOUNCEMENT_ID, activity.getId()) - .withValue(Announcements.ANNOUNCEMENT_DATE, activity.getUpdated().getValue()) - .withValue(Announcements.ANNOUNCEMENT_TITLE, activity.getTitle()) - .withValue(Announcements.ANNOUNCEMENT_ACTIVITY_JSON, activity.toPrettyString()) - .withValue(Announcements.ANNOUNCEMENT_URL, activity.getUrl()) - .build()); - } - - return batch; - } - - private static void appendIfNotEmpty(StringBuilder sb, String s) { - if (!TextUtils.isEmpty(s)) { - sb.append(s); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/BlocksHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/BlocksHandler.java deleted file mode 100644 index be631fa0c2..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/BlocksHandler.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 com.google.samples.apps.iosched.io; - -import com.google.samples.apps.iosched.io.model.Block; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.util.ParserUtils; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.net.Uri; - -import java.util.ArrayList; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGW; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - - -public class BlocksHandler extends JSONHandler { - private static final String TAG = makeLogTag(BlocksHandler.class); - private ArrayList mBlocks = new ArrayList(); - - public BlocksHandler(Context context) { - super(context); - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Blocks.CONTENT_URI); - list.add(ContentProviderOperation.newDelete(uri).build()); - for (Block block : mBlocks) { - outputBlock(block, list); - } - } - - @Override - public void process(JsonElement element) { - for (Block block : new Gson().fromJson(element, Block[].class)) { - mBlocks.add(block); - } - } - - private static void outputBlock(Block block, ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Blocks.CONTENT_URI); - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - String title = block.title != null ? block.title : ""; - String meta = block.subtitle != null ? block.subtitle : ""; - - String type = block.type; - if ( ! ScheduleContract.Blocks.isValidBlockType(type)) { - LOGW(TAG, "block from "+block.start+" to "+block.end+" has unrecognized type (" - +type+"). Using "+ ScheduleContract.Blocks.BLOCK_TYPE_BREAK +" instead."); - type = ScheduleContract.Blocks.BLOCK_TYPE_BREAK; - } - - long startTimeL = ParserUtils.parseTime(block.start); - long endTimeL = ParserUtils.parseTime(block.end); - final String blockId = ScheduleContract.Blocks.generateBlockId(startTimeL, endTimeL); - builder.withValue(ScheduleContract.Blocks.BLOCK_ID, blockId); - builder.withValue(ScheduleContract.Blocks.BLOCK_TITLE, title); - builder.withValue(ScheduleContract.Blocks.BLOCK_START, startTimeL); - builder.withValue(ScheduleContract.Blocks.BLOCK_END, endTimeL); - builder.withValue(ScheduleContract.Blocks.BLOCK_TYPE, type); - builder.withValue(ScheduleContract.Blocks.BLOCK_SUBTITLE, meta); - list.add(builder.build()); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/ExpertsHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/ExpertsHandler.java deleted file mode 100644 index 1a0930f5b4..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/ExpertsHandler.java +++ /dev/null @@ -1,173 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.io.model.Expert; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class ExpertsHandler extends JSONHandler { - - private static final String TAG = makeLogTag(ExpertsHandler.class); - - private HashMap mExperts = new HashMap(); - - public ExpertsHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - if (Config.hasExpertsDirectoryExpired()) { - return; - } - for (Expert expert : new Gson().fromJson(element, Expert[].class)) { - mExperts.put(expert.id, expert); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - if (Config.hasExpertsDirectoryExpired()) { - return; - } - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Experts.CONTENT_URI); - HashMap expertsHashcodes = loadExpertHashcodes(); - HashSet expertsToKeep = new HashSet(); - boolean isIncrementalUpdate = expertsHashcodes != null && expertsHashcodes.size() > 0; - - if (isIncrementalUpdate) { - LOGD(TAG, "Doing incremental update for experts."); - } else { - LOGD(TAG, "Doing FULL (non incremental) update for experts."); - list.add(ContentProviderOperation.newDelete(uri).build()); - } - - int updatedExperts = 0; - for (Expert expert : mExperts.values()) { - String hashCode = expert.getImportHashCode(); - expertsToKeep.add(expert.id); - - // Add the expert, if necessary - if (!isIncrementalUpdate || !expertsHashcodes.containsKey(expert.id) || - !expertsHashcodes.get(expert.id).equals(hashCode)) { - ++updatedExperts; - boolean isNew = !isIncrementalUpdate || !expertsHashcodes.containsKey(expert.id); - buildExpert(isNew, expert, list); - } - } - - int deletedExperts = 0; - if (isIncrementalUpdate) { - for (String expertId : expertsHashcodes.keySet()) { - if (!expertsToKeep.contains(expertId)) { - buildDeleteOperation(expertId, list); - ++deletedExperts; - } - } - } - - LOGD(TAG, "Experts: " + (isIncrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " + - updatedExperts + " to update, " + deletedExperts + " to delete. New total: " + - mExperts.size()); - } - - private void buildExpert(boolean isInsert, Expert expert, - ArrayList list) { - Uri allExpertsUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Experts.CONTENT_URI); - Uri thisExpertUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Experts.buildExpertUri(expert.id)); - - ContentProviderOperation.Builder builder; - if (isInsert) { - builder = ContentProviderOperation.newInsert(allExpertsUri); - } else { - builder = ContentProviderOperation.newUpdate(thisExpertUri); - } - - list.add(builder.withValue(ScheduleContract.Experts.UPDATED, System.currentTimeMillis()) - .withValue(ScheduleContract.Experts.EXPERT_ID, expert.id) - .withValue(ScheduleContract.Experts.EXPERT_NAME, expert.name) - .withValue(ScheduleContract.Experts.EXPERT_IMAGE_URL, expert.imageUrl) - .withValue(ScheduleContract.Experts.EXPERT_TITLE, expert.title) - .withValue(ScheduleContract.Experts.EXPERT_ABSTRACT, expert.bio) - .withValue(ScheduleContract.Experts.EXPERT_URL, expert.url) - .withValue(ScheduleContract.Experts.EXPERT_COUNTRY, expert.country) - .withValue(ScheduleContract.Experts.EXPERT_CITY, expert.city) - .withValue(ScheduleContract.Experts.EXPERT_ATTENDING, expert.attending) - .withValue(ScheduleContract.Experts.EXPERT_IMPORT_HASHCODE, - expert.getImportHashCode()) - .build()); - } - - private void buildDeleteOperation(String expertId, ArrayList list) { - Uri expertUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Experts.buildExpertUri(expertId)); - list.add(ContentProviderOperation.newDelete(expertUri).build()); - } - - private HashMap loadExpertHashcodes() { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Experts.CONTENT_URI); - Cursor cursor = mContext.getContentResolver().query(uri, ExpertHashcodeQuery.PROJECTION, - null, null, null); - if (cursor == null) { - LOGE(TAG, "Error querying expert hashcodes (got null cursor"); - return null; - } - if (cursor.getCount() < 1) { - LOGE(TAG, "Error querying expert hashcodes (no records returned)"); - return null; - } - HashMap result = new HashMap(); - while (cursor.moveToNext()) { - String expertId = cursor.getString(ExpertHashcodeQuery.EXPERT_ID); - String hashcode = cursor.getString(ExpertHashcodeQuery.EXPERT_IMPORT_HASHCODE); - result.put(expertId, hashcode == null ? "" : hashcode); - } - cursor.close(); - return result; - } - - private interface ExpertHashcodeQuery { - String[] PROJECTION = { - BaseColumns._ID, - ScheduleContract.Experts.EXPERT_ID, - ScheduleContract.Experts.EXPERT_IMPORT_HASHCODE, - }; - final int _ID = 0; - final int EXPERT_ID = 1; - final int EXPERT_IMPORT_HASHCODE = 2; - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/HandlerException.java b/android/src/main/java/com/google/samples/apps/iosched/io/HandlerException.java deleted file mode 100644 index f678f56eb1..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/HandlerException.java +++ /dev/null @@ -1,48 +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 com.google.samples.apps.iosched.io; - -import java.io.IOException; - -/** - * General {@link IOException} that indicates a problem occurred while parsing or applying a {@link - * JSONHandler}. - */ -public class HandlerException extends IOException { - - public HandlerException() { - super(); - } - - public HandlerException(String message) { - super(message); - } - - public HandlerException(String message, Throwable cause) { - super(message); - initCause(cause); - } - - @Override - public String toString() { - if (getCause() != null) { - return getLocalizedMessage() + ": " + getCause(); - } else { - return getLocalizedMessage(); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/HashtagsHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/HashtagsHandler.java deleted file mode 100644 index fc37f10dcc..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/HashtagsHandler.java +++ /dev/null @@ -1,76 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.graphics.Color; -import android.net.Uri; - -import com.google.samples.apps.iosched.io.model.Hashtag; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.HashMap; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class HashtagsHandler extends JSONHandler { - - private static final String TAG = makeLogTag(HashtagsHandler.class); - private HashMap mHashtags = new HashMap(); - - public HashtagsHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - LOGD(TAG, "process"); - for (Hashtag hashtag : new Gson().fromJson(element, Hashtag[].class)) { - mHashtags.put(hashtag.name, hashtag); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - LOGD(TAG, "makeContentProviderOperations"); - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Hashtags.CONTENT_URI); - // Remove all the current entries - list.add(ContentProviderOperation.newDelete(uri).build()); - // Insert hashtags - for (Hashtag hashtag : mHashtags.values()) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - builder.withValue(ScheduleContract.Hashtags.HASHTAG_NAME, hashtag.name); - builder.withValue(ScheduleContract.Hashtags.HASHTAG_DESCRIPTION, hashtag.description); - try { - builder.withValue(ScheduleContract.Hashtags.HASHTAG_COLOR, - Color.parseColor(hashtag.color)); - } catch (IllegalArgumentException e) { - builder.withValue(ScheduleContract.Hashtags.HASHTAG_COLOR, Color.BLACK); - } - builder.withValue(ScheduleContract.Hashtags.HASHTAG_ORDER, hashtag.order); - list.add(builder.build()); - } - LOGD(TAG, "Hashtags: " + mHashtags.size()); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/JSONHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/JSONHandler.java deleted file mode 100644 index db8771b489..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/JSONHandler.java +++ /dev/null @@ -1,61 +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 com.google.samples.apps.iosched.io; - -import com.google.gson.JsonElement; - -import android.content.ContentProviderOperation; -import android.content.Context; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; - -public abstract class JSONHandler { - - protected static Context mContext; - - public JSONHandler(Context context) { - mContext = context; - } - - public abstract void makeContentProviderOperations(ArrayList list); - - public abstract void process(JsonElement element); - - public static String parseResource(Context context, int resource) throws IOException { - InputStream is = context.getResources().openRawResource(resource); - Writer writer = new StringWriter(); - char[] buffer = new char[1024]; - try { - Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); - int n; - while ((n = reader.read(buffer)) != -1) { - writer.write(buffer, 0, n); - } - } finally { - is.close(); - } - - return writer.toString(); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/MapPropertyHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/MapPropertyHandler.java deleted file mode 100644 index c07b3d928e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/MapPropertyHandler.java +++ /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. - */ - -package com.google.samples.apps.iosched.io; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.net.Uri; - -import com.google.samples.apps.iosched.io.map.model.MapData; -import com.google.samples.apps.iosched.io.map.model.MapConfig; -import com.google.samples.apps.iosched.io.map.model.Marker; -import com.google.samples.apps.iosched.io.map.model.Tile; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.util.MapUtils; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class MapPropertyHandler extends JSONHandler { - private static final String TAG = makeLogTag(MapPropertyHandler.class); - - // maps floor# to tile overlay for that floor - private HashMap mTileOverlays = new HashMap(); - - // maps floor# to a list of markers on that floor - private HashMap> mMarkers = new HashMap>(); - - public MapPropertyHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (MapData mapData : new Gson().fromJson(element, MapData[].class)) { - if (mapData.config != null) { - processConfig(mapData.config); - } - if (mapData.tiles != null) { - processTileOverlays(mapData.tiles); - } - if (mapData.markers != null) { - processMarkers(mapData.markers); - } - } - } - - public Collection getTileOverlays() { - return mTileOverlays.values(); - } - - private void processConfig(MapConfig mapConfig) { - MapUtils.setMyLocationEnabled(mContext, mapConfig.enableMyLocation); - } - - private void processTileOverlays(java.util.Map mapTiles) { - for (Map.Entry entry : mapTiles.entrySet()) { - mTileOverlays.put(entry.getKey(), entry.getValue()); - } - } - - private void processMarkers(java.util.Map markers) { - for (String floor : markers.keySet()) { - if (!mMarkers.containsKey(floor)) { - mMarkers.put(floor, new ArrayList()); - } - for (Marker marker : markers.get(floor)) { - mMarkers.get(floor).add(marker); - } - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - buildMarkers(list); - buildTiles(list); - } - - private void buildMarkers(ArrayList list) { - Uri uri = ScheduleContract - .addCallerIsSyncAdapterParameter(ScheduleContract.MapMarkers.CONTENT_URI); - - list.add(ContentProviderOperation.newDelete(uri).build()); - - for (String floor : mMarkers.keySet()) { - for (Marker marker : mMarkers.get(floor)) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - builder.withValue(ScheduleContract.MapMarkers.MARKER_ID, marker.id); - builder.withValue(ScheduleContract.MapMarkers.MARKER_FLOOR, floor); - builder.withValue(ScheduleContract.MapMarkers.MARKER_LABEL, marker.title); - builder.withValue(ScheduleContract.MapMarkers.MARKER_LATITUDE, marker.lat); - builder.withValue(ScheduleContract.MapMarkers.MARKER_LONGITUDE, marker.lng); - builder.withValue(ScheduleContract.MapMarkers.MARKER_TYPE, marker.type); - list.add(builder.build()); - } - } - } - - private void buildTiles(ArrayList list) { - Uri uri = ScheduleContract - .addCallerIsSyncAdapterParameter(ScheduleContract.MapTiles.CONTENT_URI); - - list.add(ContentProviderOperation.newDelete(uri).build()); - - for (String floor : mTileOverlays.keySet()) { - Tile tileOverlay = mTileOverlays.get(floor); - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - builder.withValue(ScheduleContract.MapTiles.TILE_FLOOR, floor); - builder.withValue(ScheduleContract.MapTiles.TILE_FILE, tileOverlay.filename); - builder.withValue(ScheduleContract.MapTiles.TILE_URL, tileOverlay.url); - list.add(builder.build()); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/PartnersHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/PartnersHandler.java deleted file mode 100644 index 93e3fbf5b8..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/PartnersHandler.java +++ /dev/null @@ -1,78 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.net.Uri; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.samples.apps.iosched.io.model.Partner; -import com.google.samples.apps.iosched.provider.ScheduleContract; - -import java.util.ArrayList; -import java.util.HashMap; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * A class for transforming Partners JSON to equivalent operations on the content provider. - * - * Processes incoming json into a map of {partnerId: Partner, ...}, which can then be transformed - * into a set of content provider operations that will make the DB mirror this data. - */ -public class PartnersHandler extends JSONHandler { - private static final String TAG = makeLogTag(PartnersHandler.class); - private HashMap mPartners = new HashMap(); - - public PartnersHandler(Context context) { - super(context); - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Partners.CONTENT_URI); - - // TODO: Think about incremental updates... - list.add(ContentProviderOperation.newDelete(uri).build()); - - for (Partner partner : mPartners.values()) { - list.add(buildPartnerInsertOperation(partner)); - } - } - - @Override - public void process(JsonElement element) { - // Does partner data expire like experts data does? - for (Partner partner : new Gson().fromJson(element, Partner[].class)) { - mPartners.put(partner.id, partner); - } - } - - private ContentProviderOperation buildPartnerInsertOperation(Partner partner) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert( - ScheduleContract.Partners.CONTENT_URI); - return builder.withValue(ScheduleContract.Partners.PARTNER_ID, partner.id) - .withValue(ScheduleContract.Partners.PARTNER_NAME, partner.name) - .withValue(ScheduleContract.Partners.PARTNER_DESC, partner.desc) - .withValue(ScheduleContract.Partners.PARTNER_WEBSITE_URL, partner.website) - .withValue(ScheduleContract.Partners.PARTNER_LOGO_URL, partner.logo) - .build(); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/RoomsHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/RoomsHandler.java deleted file mode 100644 index 6fd94065e9..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/RoomsHandler.java +++ /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. - */ - -package com.google.samples.apps.iosched.io; - -import com.google.samples.apps.iosched.io.model.Room; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.net.Uri; - -import java.util.ArrayList; -import java.util.HashMap; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class RoomsHandler extends JSONHandler { - private static final String TAG = makeLogTag(RoomsHandler.class); - - // map from room ID to Room model object - private HashMap mRooms = new HashMap(); - - public RoomsHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (Room room : new Gson().fromJson(element, Room[].class)) { - mRooms.put(room.id, room); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Rooms.CONTENT_URI); - - // The list of rooms is not large, so for simplicity we delete all of them and repopulate - list.add(ContentProviderOperation.newDelete(uri).build()); - for (Room room : mRooms.values()) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - builder.withValue(ScheduleContract.Rooms.ROOM_ID, room.id); - builder.withValue(ScheduleContract.Rooms.ROOM_NAME, room.name); - builder.withValue(ScheduleContract.Rooms.ROOM_FLOOR, room.floor); - list.add(builder.build()); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/SearchSuggestHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/SearchSuggestHandler.java deleted file mode 100644 index 7ebb50bdde..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/SearchSuggestHandler.java +++ /dev/null @@ -1,61 +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 com.google.samples.apps.iosched.io; - -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import android.app.SearchManager; -import android.content.ContentProviderOperation; -import android.content.Context; -import android.net.Uri; - -import java.util.ArrayList; -import java.util.HashSet; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGE; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class SearchSuggestHandler extends JSONHandler { - private static final String TAG = makeLogTag(SpeakersHandler.class); - HashSet mSuggestions = new HashSet(); - - public SearchSuggestHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (String word : new Gson().fromJson(element, String[].class)) { - mSuggestions.add(word); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.SearchSuggest.CONTENT_URI); - - list.add(ContentProviderOperation.newDelete(uri).build()); - for (String word : mSuggestions) { - list.add(ContentProviderOperation.newInsert(uri) - .withValue(SearchManager.SUGGEST_COLUMN_TEXT_1, word) - .build()); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/SessionsHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/SessionsHandler.java deleted file mode 100644 index 549e1fd1a1..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/SessionsHandler.java +++ /dev/null @@ -1,311 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.net.Uri; -import android.provider.BaseColumns; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.io.model.Session; -import com.google.samples.apps.iosched.io.model.Speaker; -import com.google.samples.apps.iosched.io.model.Tag; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.provider.ScheduleDatabase; -import com.google.samples.apps.iosched.util.TimeUtils; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class SessionsHandler extends JSONHandler { - private static final String TAG = makeLogTag(SessionsHandler.class); - private HashMap mSessions = new HashMap(); - private HashMap mTagMap = null; - private HashMap mSpeakerMap = null; - private int mDefaultSessionColor; - - - public SessionsHandler(Context context) { - super(context); - mDefaultSessionColor = mContext.getResources().getColor(R.color.default_session_color); - } - - @Override - public void process(JsonElement element) { - for (Session session : new Gson().fromJson(element, Session[].class)) { - mSessions.put(session.id, session); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Sessions.CONTENT_URI); - - // build a map of session to session import hashcode so we know what to update, - // what to insert, and what to delete - HashMap sessionHashCodes = loadSessionHashCodes(); - boolean incrementalUpdate = (sessionHashCodes != null) && (sessionHashCodes.size() > 0); - - // set of sessions that we want to keep after the sync - HashSet sessionsToKeep = new HashSet(); - - if (incrementalUpdate) { - LOGD(TAG, "Doing incremental update for sessions."); - } else { - LOGD(TAG, "Doing full (non-incremental) update for sessions."); - list.add(ContentProviderOperation.newDelete(uri).build()); - } - - int updatedSessions = 0; - for (Session session : mSessions.values()) { - // Set the session grouping order in the object, so it can be used in hash calculation - session.groupingOrder = computeTypeOrder(session); - - // compute the incoming session's hashcode to figure out if we need to update - String hashCode = session.getImportHashCode(); - sessionsToKeep.add(session.id); - - // add session, if necessary - if (!incrementalUpdate || !sessionHashCodes.containsKey(session.id) || - !sessionHashCodes.get(session.id).equals(hashCode)) { - ++updatedSessions; - boolean isNew = !incrementalUpdate || !sessionHashCodes.containsKey(session.id); - buildSession(isNew, session, list); - - // add relationships to speakers and track - buildSessionSpeakerMapping(session, list); - buildTagsMapping(session, list); - } - } - - int deletedSessions = 0; - if (incrementalUpdate) { - for (String sessionId : sessionHashCodes.keySet()) { - if (!sessionsToKeep.contains(sessionId)) { - buildDeleteOperation(sessionId, list); - ++deletedSessions; - } - } - } - - LOGD(TAG, "Sessions: " + (incrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " + - updatedSessions + " to update, " + deletedSessions + " to delete. New total: " + - mSessions.size()); - } - - private void buildDeleteOperation(String sessionId, List list) { - Uri sessionUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Sessions.buildSessionUri(sessionId)); - list.add(ContentProviderOperation.newDelete(sessionUri).build()); - } - - private HashMap loadSessionHashCodes() { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Sessions.CONTENT_URI); - LOGD(TAG, "Loading session hashcodes for session import optimization."); - Cursor cursor = mContext.getContentResolver().query(uri, SessionHashcodeQuery.PROJECTION, - null, null, null); - if (cursor == null || cursor.getCount() < 1) { - LOGW(TAG, "Warning: failed to load session hashcodes. Not optimizing session import."); - if (cursor != null) { - cursor.close(); - } - return null; - } - HashMap hashcodeMap = new HashMap(); - while (cursor.moveToNext()) { - String sessionId = cursor.getString(SessionHashcodeQuery.SESSION_ID); - String hashcode = cursor.getString(SessionHashcodeQuery.SESSION_IMPORT_HASHCODE); - hashcodeMap.put(sessionId, hashcode == null ? "" : hashcode); - } - LOGD(TAG, "Session hashcodes loaded for " + hashcodeMap.size() + " sessions."); - cursor.close(); - return hashcodeMap; - } - - StringBuilder mStringBuilder = new StringBuilder(); - - private void buildSession(boolean isInsert, - Session session, ArrayList list) { - ContentProviderOperation.Builder builder; - Uri allSessionsUri = ScheduleContract - .addCallerIsSyncAdapterParameter(ScheduleContract.Sessions.CONTENT_URI); - Uri thisSessionUri = ScheduleContract - .addCallerIsSyncAdapterParameter(ScheduleContract.Sessions.buildSessionUri( - session.id)); - - if (isInsert) { - builder = ContentProviderOperation.newInsert(allSessionsUri); - } else { - builder = ContentProviderOperation.newUpdate(thisSessionUri); - } - - String speakerNames = ""; - if (mSpeakerMap != null) { - // build human-readable list of speakers - mStringBuilder.setLength(0); - for (int i = 0; i < session.speakers.length; ++i) { - if (mSpeakerMap.containsKey(session.speakers[i])) { - mStringBuilder - .append(i == 0 ? "" : i == session.speakers.length - 1 ? " and " : ", ") - .append(mSpeakerMap.get(session.speakers[i]).name.trim()); - } else { - LOGW(TAG, "Unknown speaker ID " + session.speakers[i] + " in session " + session.id); - } - } - speakerNames = mStringBuilder.toString(); - } else { - LOGE(TAG, "Can't build speaker names -- speaker map is null."); - } - - int color = mDefaultSessionColor; - try { - if (!TextUtils.isEmpty(session.color)) { - color = Color.parseColor(session.color); - } - } catch (IllegalArgumentException ex) { - LOGD(TAG, "Ignoring invalid formatted session color: "+session.color); - } - builder.withValue(ScheduleContract.SyncColumns.UPDATED, System.currentTimeMillis()) - .withValue(ScheduleContract.Sessions.SESSION_ID, session.id) - .withValue(ScheduleContract.Sessions.SESSION_LEVEL, null) // Not available - .withValue(ScheduleContract.Sessions.SESSION_TITLE, session.title) - .withValue(ScheduleContract.Sessions.SESSION_ABSTRACT, session.description) - .withValue(ScheduleContract.Sessions.SESSION_HASHTAG, session.hashtag) - .withValue(ScheduleContract.Sessions.SESSION_START, TimeUtils.timestampToMillis(session.startTimestamp, 0)) - .withValue(ScheduleContract.Sessions.SESSION_END, TimeUtils.timestampToMillis(session.endTimestamp, 0)) - .withValue(ScheduleContract.Sessions.SESSION_TAGS, session.makeTagsList()) - // Note: we store this comma-separated list of tags IN ADDITION - // to storing the tags in proper relational format (in the sessions_tags - // relationship table). This is because when querying for sessions, - // we don't want to incur the performance penalty of having to do a - // subquery for every record to figure out the list of tags of each session. - .withValue(ScheduleContract.Sessions.SESSION_SPEAKER_NAMES, speakerNames) - // Note: we store the human-readable list of speakers (which is redundant - // with the sessions_speakers relationship table) so that we can - // display it easily in lists without having to make an additional DB query - // (or another join) for each record. - .withValue(ScheduleContract.Sessions.SESSION_KEYWORDS, null) // Not available - .withValue(ScheduleContract.Sessions.SESSION_URL, session.url) - .withValue(ScheduleContract.Sessions.SESSION_LIVESTREAM_URL, - session.isLivestream ? session.youtubeUrl : null) - .withValue(ScheduleContract.Sessions.SESSION_MODERATOR_URL, null) // Not available - .withValue(ScheduleContract.Sessions.SESSION_REQUIREMENTS, null) // Not available - .withValue(ScheduleContract.Sessions.SESSION_YOUTUBE_URL, - session.isLivestream ? null : session.youtubeUrl) - .withValue(ScheduleContract.Sessions.SESSION_PDF_URL, null) // Not available - .withValue(ScheduleContract.Sessions.SESSION_NOTES_URL, null) // Not available - .withValue(ScheduleContract.Sessions.ROOM_ID, session.room) - .withValue(ScheduleContract.Sessions.SESSION_GROUPING_ORDER, session.groupingOrder) - .withValue(ScheduleContract.Sessions.SESSION_IMPORT_HASHCODE, - session.getImportHashCode()) - .withValue(ScheduleContract.Sessions.SESSION_MAIN_TAG, session.mainTag) - .withValue(ScheduleContract.Sessions.SESSION_CAPTIONS_URL, session.captionsUrl) - .withValue(ScheduleContract.Sessions.SESSION_PHOTO_URL, session.photoUrl) - .withValue(ScheduleContract.Sessions.SESSION_RELATED_CONTENT, session.relatedContent) - .withValue(ScheduleContract.Sessions.SESSION_COLOR, color); - list.add(builder.build()); - } - - // The type order of a session is the order# (in its category) of the tag that indicates - // its type. So if we sort sessions by type order, they will be neatly grouped by type, - // with the types appearing in the order given by the tag category that represents the - // concept of session type. - private int computeTypeOrder(Session session) { - int order = Integer.MAX_VALUE; - int keynoteOrder = -1; - if (mTagMap == null) { - throw new IllegalStateException("Attempt to compute type order without tag map."); - } - for (String tagId : session.tags) { - if (Config.Tags.SPECIAL_KEYNOTE.equals(tagId)) { - return keynoteOrder; - } - Tag tag = mTagMap.get(tagId); - if (tag != null && Config.Tags.SESSION_GROUPING_TAG_CATEGORY.equals(tag.category)) { - if (tag.order_in_category < order) { - order = tag.order_in_category; - } - } - } - return order; - } - - private void buildSessionSpeakerMapping(Session session, - ArrayList list) { - final Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Sessions.buildSpeakersDirUri(session.id)); - - // delete any existing relationship between this session and speakers - list.add(ContentProviderOperation.newDelete(uri).build()); - - // add relationship records to indicate the speakers for this session - if (session.speakers != null) { - for (String speakerId : session.speakers) { - list.add(ContentProviderOperation.newInsert(uri) - .withValue(ScheduleDatabase.SessionsSpeakers.SESSION_ID, session.id) - .withValue(ScheduleDatabase.SessionsSpeakers.SPEAKER_ID, speakerId) - .build()); - } - } - } - - private void buildTagsMapping(Session session, ArrayList list) { - final Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Sessions.buildTagsDirUri(session.id)); - - // delete any existing mappings - list.add(ContentProviderOperation.newDelete(uri).build()); - - // add a mapping (a session+tag tuple) for each tag in the session - for (String tag : session.tags) { - list.add(ContentProviderOperation.newInsert(uri) - .withValue(ScheduleDatabase.SessionsTags.SESSION_ID, session.id) - .withValue(ScheduleDatabase.SessionsTags.TAG_ID, tag).build()); - } - } - - public void setTagMap(HashMap tagMap) { - mTagMap = tagMap; - } - public void setSpeakerMap(HashMap speakerMap) { - mSpeakerMap = speakerMap; - } - - private interface SessionHashcodeQuery { - String[] PROJECTION = { - BaseColumns._ID, - ScheduleContract.Sessions.SESSION_ID, - ScheduleContract.Sessions.SESSION_IMPORT_HASHCODE - }; - int _ID = 0; - int SESSION_ID = 1; - int SESSION_IMPORT_HASHCODE = 2; - }; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/SpeakersHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/SpeakersHandler.java deleted file mode 100644 index 9f07456265..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/SpeakersHandler.java +++ /dev/null @@ -1,163 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; - -import com.google.samples.apps.iosched.io.model.Speaker; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class SpeakersHandler extends JSONHandler { - private static final String TAG = makeLogTag(SpeakersHandler.class); - private HashMap mSpeakers = new HashMap(); - - public SpeakersHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (Speaker speaker : new Gson().fromJson(element, Speaker[].class)) { - mSpeakers.put(speaker.id, speaker); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Speakers.CONTENT_URI); - HashMap speakerHashcodes = loadSpeakerHashcodes(); - HashSet speakersToKeep = new HashSet(); - boolean isIncrementalUpdate = speakerHashcodes != null && speakerHashcodes.size() > 0; - - if (isIncrementalUpdate) { - LOGD(TAG, "Doing incremental update for speakers."); - } else { - LOGD(TAG, "Doing FULL (non incremental) update for speakers."); - list.add(ContentProviderOperation.newDelete(uri).build()); - } - - int updatedSpeakers = 0; - for (Speaker speaker : mSpeakers.values()) { - String hashCode = speaker.getImportHashcode(); - speakersToKeep.add(speaker.id); - - // add speaker, if necessary - if (!isIncrementalUpdate || !speakerHashcodes.containsKey(speaker.id) || - !speakerHashcodes.get(speaker.id).equals(hashCode)) { - ++updatedSpeakers; - boolean isNew = !isIncrementalUpdate || !speakerHashcodes.containsKey(speaker.id); - buildSpeaker(isNew, speaker, list); - } - } - - int deletedSpeakers = 0; - if (isIncrementalUpdate) { - for (String speakerId : speakerHashcodes.keySet()) { - if (!speakersToKeep.contains(speakerId)) { - buildDeleteOperation(speakerId, list); - ++deletedSpeakers; - } - } - } - - LOGD(TAG, "Speakers: " + (isIncrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " + - updatedSpeakers + " to update, " + deletedSpeakers + " to delete. New total: " + - mSpeakers.size()); - } - - private void buildSpeaker(boolean isInsert, Speaker speaker, - ArrayList list) { - Uri allSpeakersUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Speakers.CONTENT_URI); - Uri thisSpeakerUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Speakers.buildSpeakerUri(speaker.id)); - - ContentProviderOperation.Builder builder; - if (isInsert) { - builder = ContentProviderOperation.newInsert(allSpeakersUri); - } else { - builder = ContentProviderOperation.newUpdate(thisSpeakerUri); - } - - list.add(builder.withValue(ScheduleContract.SyncColumns.UPDATED, System.currentTimeMillis()) - .withValue(ScheduleContract.Speakers.SPEAKER_ID, speaker.id) - .withValue(ScheduleContract.Speakers.SPEAKER_NAME, speaker.name) - .withValue(ScheduleContract.Speakers.SPEAKER_ABSTRACT, speaker.bio) - .withValue(ScheduleContract.Speakers.SPEAKER_COMPANY, speaker.company) - .withValue(ScheduleContract.Speakers.SPEAKER_IMAGE_URL, speaker.thumbnailUrl) - .withValue(ScheduleContract.Speakers.SPEAKER_URL, speaker.plusoneUrl) - .withValue(ScheduleContract.Speakers.SPEAKER_IMPORT_HASHCODE, - speaker.getImportHashcode()) - .build()); - } - - private void buildDeleteOperation(String speakerId, ArrayList list) { - Uri speakerUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Speakers.buildSpeakerUri(speakerId)); - list.add(ContentProviderOperation.newDelete(speakerUri).build()); - } - - private HashMap loadSpeakerHashcodes() { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Speakers.CONTENT_URI); - Cursor cursor = mContext.getContentResolver().query(uri, SpeakerHashcodeQuery.PROJECTION, - null, null, null); - if (cursor == null) { - LOGE(TAG, "Error querying speaker hashcodes (got null cursor)"); - return null; - } - if (cursor.getCount() < 1) { - LOGE(TAG, "Error querying speaker hashcodes (no records returned)"); - return null; - } - HashMap result = new HashMap(); - while (cursor.moveToNext()) { - String speakerId = cursor.getString(SpeakerHashcodeQuery.SPEAKER_ID); - String hashcode = cursor.getString(SpeakerHashcodeQuery.SPEAKER_IMPORT_HASHCODE); - result.put(speakerId, hashcode == null ? "" : hashcode); - } - cursor.close(); - return result; - } - - public HashMap getSpeakerMap() { - return mSpeakers; - } - - private interface SpeakerHashcodeQuery { - String[] PROJECTION = { - BaseColumns._ID, - ScheduleContract.Speakers.SPEAKER_ID, - ScheduleContract.Speakers.SPEAKER_IMPORT_HASHCODE - }; - final int _ID = 0; - final int SPEAKER_ID = 1; - final int SPEAKER_IMPORT_HASHCODE = 2; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/TagsHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/TagsHandler.java deleted file mode 100644 index 93acbf415e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/TagsHandler.java +++ /dev/null @@ -1,73 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.graphics.Color; -import android.net.Uri; - -import com.google.samples.apps.iosched.io.model.Tag; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.HashMap; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class TagsHandler extends JSONHandler { - private static final String TAG = makeLogTag(TagsHandler.class); - - private HashMap mTags = new HashMap(); - - public TagsHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (Tag tag : new Gson().fromJson(element, Tag[].class)) { - mTags.put(tag.tag, tag); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Tags.CONTENT_URI); - - // since the number of tags is very small, for simplicity we delete them all and reinsert - list.add(ContentProviderOperation.newDelete(uri).build()); - for (Tag tag : mTags.values()) { - ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); - builder.withValue(ScheduleContract.Tags.TAG_ID, tag.tag); - builder.withValue(ScheduleContract.Tags.TAG_CATEGORY, tag.category); - builder.withValue(ScheduleContract.Tags.TAG_NAME, tag.name); - builder.withValue(ScheduleContract.Tags.TAG_ORDER_IN_CATEGORY, tag.order_in_category); - builder.withValue(ScheduleContract.Tags.TAG_ABSTRACT, tag._abstract); - builder.withValue(ScheduleContract.Tags.TAG_COLOR, tag.color==null ? - Color.LTGRAY : Color.parseColor(tag.color)); - list.add(builder.build()); - } - } - - public HashMap getTagMap() { - return mTags; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/VideosHandler.java b/android/src/main/java/com/google/samples/apps/iosched/io/VideosHandler.java deleted file mode 100644 index 57438358b9..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/VideosHandler.java +++ /dev/null @@ -1,184 +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 com.google.samples.apps.iosched.io; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.io.model.Video; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class VideosHandler extends JSONHandler { - private static final String TAG = makeLogTag(VideosHandler.class); - private HashMap mVideos = new HashMap(); - - public VideosHandler(Context context) { - super(context); - } - - @Override - public void process(JsonElement element) { - for (Video video : new Gson().fromJson(element, Video[].class)) { - if (TextUtils.isEmpty(video.id)) { - LOGW(TAG, "Video without valid ID. Using VID instead: " + video.vid); - video.id = video.vid; - } - mVideos.put(video.id, video); - } - } - - @Override - public void makeContentProviderOperations(ArrayList list) { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Videos.CONTENT_URI); - HashMap videoHashcodes = loadVideoHashcodes(); - HashSet videosToKeep = new HashSet(); - boolean isIncrementalUpdate = videoHashcodes != null && videoHashcodes.size() > 0; - - if (isIncrementalUpdate) { - LOGD(TAG, "Doing incremental update for videos."); - } else { - LOGD(TAG, "Doing FULL (non incremental) update for videos."); - list.add(ContentProviderOperation.newDelete(uri).build()); - } - - int updatedVideos = 0; - for (Video video : mVideos.values()) { - String hashCode = video.getImportHashcode(); - videosToKeep.add(video.id); - - // add video, if necessary - if (!isIncrementalUpdate || !videoHashcodes.containsKey(video.id) || - !videoHashcodes.get(video.id).equals(hashCode)) { - ++updatedVideos; - boolean isNew = !isIncrementalUpdate || !videoHashcodes.containsKey(video.id); - buildVideo(isNew, video, list); - } - } - - int deletedVideos = 0; - if (isIncrementalUpdate) { - for (String videoId : videoHashcodes.keySet()) { - if (!videosToKeep.contains(videoId)) { - buildDeleteOperation(videoId, list); - ++deletedVideos; - } - } - } - - LOGD(TAG, "Videos: " + (isIncrementalUpdate ? "INCREMENTAL" : "FULL") + " update. " + - updatedVideos + " to update, " + deletedVideos + " to delete. New total: " + - mVideos.size()); - } - - private void buildVideo(boolean isInsert, Video video, - ArrayList list) { - Uri allVideosUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Videos.CONTENT_URI); - Uri thisVideoUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Videos.buildVideoUri(video.id)); - - ContentProviderOperation.Builder builder; - if (isInsert) { - builder = ContentProviderOperation.newInsert(allVideosUri); - } else { - builder = ContentProviderOperation.newUpdate(thisVideoUri); - } - - if (TextUtils.isEmpty(video.vid)) { - LOGW(TAG, "Ignoring video with missing video ID."); - return; - } - - String thumbUrl = video.thumbnailUrl; - if (TextUtils.isEmpty(thumbUrl)) { - // Oops, missing thumbnail URL. Let's improvise. - // NOTE: this method of obtaining a thumbnail URL from the video ID - // is unofficial and might not work in the future; that's why we use - // it only as a fallback in case we don't get a thumbnail URL in the incoming data. - thumbUrl = String.format(Locale.US, Config.VIDEO_LIBRARY_FALLBACK_THUMB_URL_FMT, video.vid); - LOGW(TAG, "Video with missing thumbnail URL: " + video.vid - + ". Using fallback: " + thumbUrl); - } - - list.add(builder.withValue(ScheduleContract.Videos.VIDEO_ID, video.id) - .withValue(ScheduleContract.Videos.VIDEO_YEAR, video.year) - .withValue(ScheduleContract.Videos.VIDEO_TITLE, video.title.trim()) - .withValue(ScheduleContract.Videos.VIDEO_DESC, video.desc) - .withValue(ScheduleContract.Videos.VIDEO_VID, video.vid) - .withValue(ScheduleContract.Videos.VIDEO_TOPIC, video.topic) - .withValue(ScheduleContract.Videos.VIDEO_SPEAKERS, video.speakers) - .withValue(ScheduleContract.Videos.VIDEO_THUMBNAIL_URL, thumbUrl) - .withValue(ScheduleContract.Videos.VIDEO_IMPORT_HASHCODE, - video.getImportHashcode()) - .build()); - } - - private void buildDeleteOperation(String videoId, ArrayList list) { - Uri videoUri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Videos.buildVideoUri(videoId)); - list.add(ContentProviderOperation.newDelete(videoUri).build()); - } - - private HashMap loadVideoHashcodes() { - Uri uri = ScheduleContract.addCallerIsSyncAdapterParameter( - ScheduleContract.Videos.CONTENT_URI); - Cursor cursor = mContext.getContentResolver().query(uri, VideoHashcodeQuery.PROJECTION, - null, null, null); - if (cursor == null) { - LOGE(TAG, "Error querying video hashcodes (got null cursor)"); - return null; - } - if (cursor.getCount() < 1) { - LOGE(TAG, "Error querying video hashcodes (no records returned)"); - return null; - } - HashMap result = new HashMap(); - while (cursor.moveToNext()) { - String videoId = cursor.getString(VideoHashcodeQuery.VIDEO_ID); - String hashcode = cursor.getString(VideoHashcodeQuery.VIDEO_IMPORT_HASHCODE); - result.put(videoId, hashcode == null ? "" : hashcode); - } - cursor.close(); - return result; - } - - private interface VideoHashcodeQuery { - String[] PROJECTION = { - BaseColumns._ID, - ScheduleContract.Videos.VIDEO_ID, - ScheduleContract.Videos.VIDEO_IMPORT_HASHCODE - }; - final int _ID = 0; - final int VIDEO_ID = 1; - final int VIDEO_IMPORT_HASHCODE = 2; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapConfig.java b/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapConfig.java deleted file mode 100644 index 439a9133e3..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapConfig.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.map.model; - -public class MapConfig { - public boolean enableMyLocation; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapData.java b/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapData.java deleted file mode 100644 index 5b16c3317e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/MapData.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.map.model; - -import java.util.Map; - -public class MapData { - public MapConfig config; - public Map markers; - public Map tiles; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Marker.java b/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Marker.java deleted file mode 100644 index 077d034499..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Marker.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.map.model; - -public class Marker { - public String id; - public String type; - public float lat; - public float lng; - public String title; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Tile.java b/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Tile.java deleted file mode 100644 index 521590e164..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/map/model/Tile.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.map.model; - -public class Tile { - public String filename; - public String url; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Block.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Block.java deleted file mode 100644 index 38e711aacc..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Block.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -public class Block { - public String title; - public String subtitle; - public String start; - public String end; - public String type; -} - - diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/DataManifest.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/DataManifest.java deleted file mode 100644 index 5c6350aec3..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/DataManifest.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -public class DataManifest { - public String format; - public String[] data_files; -} - - diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Expert.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Expert.java deleted file mode 100644 index 355f07e483..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Expert.java +++ /dev/null @@ -1,47 +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 com.google.samples.apps.iosched.io.model; - -import com.google.samples.apps.iosched.util.HashUtils; - -public class Expert { - public String id; - public String plusId; - public String bio; - public String name; - public String title; - public String url; - public String imageUrl; - public String country; - public String city; - public boolean attending; - - public String getImportHashCode() { - StringBuilder sb = new StringBuilder(); - sb.append("id").append(id == null ? "" : id) - .append("plusId").append(plusId == null ? "" : plusId) - .append("bio").append(bio == null ? "" : bio) - .append("name").append(name == null ? "" : name) - .append("title").append(title == null ? "" : title) - .append("url").append(url == null ? "" : url) - .append("imageUrl").append(imageUrl == null ? "" : imageUrl) - .append("country").append(country == null ? "" : country) - .append("city").append(city == null ? "" : city) - .append("attending").append(attending); - return HashUtils.computeWeakHash(sb.toString()); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Hashtag.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Hashtag.java deleted file mode 100644 index 99feb99e73..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Hashtag.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -public class Hashtag { - public String name; - public String description; - public String color; - public int order; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Partner.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Partner.java deleted file mode 100644 index 949bcedb86..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Partner.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -/** - * - */ -public class Partner { - public String id; - public String name; - public String desc; - public String website; - public String logo; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Room.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Room.java deleted file mode 100644 index 7b0d433917..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Room.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -public class Room { - public String id; - public String name; - public String floor; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Session.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Session.java deleted file mode 100644 index b611dbd983..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Session.java +++ /dev/null @@ -1,91 +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 com.google.samples.apps.iosched.io.model; - -import com.google.samples.apps.iosched.util.HashUtils; - -public class Session { - public String id; - public String url; - public String description; - public String title; - public String[] tags; - public String startTimestamp; - public String youtubeUrl; - public String[] speakers; - public String endTimestamp; - public String hashtag; - public String subtype; - public String room; - public String captionsUrl; - public String photoUrl; - public boolean isLivestream; - public String mainTag; - public String color; - public String relatedContent; - public int groupingOrder; - - public String getImportHashCode() { - StringBuilder sb = new StringBuilder(); - sb.append("id").append(id == null ? "" : id) - .append("description").append(description == null ? "" : description) - .append("title").append(title == null ? "" : title) - .append("url").append(url == null ? "" : url) - .append("startTimestamp").append(startTimestamp == null ? "" : startTimestamp) - .append("endTimestamp").append(endTimestamp == null ? "" : endTimestamp) - .append("youtubeUrl").append(youtubeUrl == null ? "" : youtubeUrl) - .append("subtype").append(subtype == null ? "" : subtype) - .append("room").append(room == null ? "" : room) - .append("hashtag").append(hashtag == null ? "" : hashtag) - .append("isLivestream").append(isLivestream ? "true" : "false") - .append("mainTag").append(mainTag) - .append("captionsUrl").append(captionsUrl) - .append("photoUrl").append(photoUrl) - .append("relatedContent").append(relatedContent) - .append("color").append(color) - .append("groupingOrder").append(groupingOrder); - for (String tag : tags) { - sb.append("tag").append(tag); - } - for (String speaker : speakers) { - sb.append("speaker").append(speaker); - } - return HashUtils.computeWeakHash(sb.toString()); - } - - public String makeTagsList() { - int i; - if (tags.length == 0) return ""; - StringBuilder sb = new StringBuilder(); - sb.append(tags[0]); - for (i = 1; i < tags.length; i++) { - sb.append(",").append(tags[i]); - } - return sb.toString(); - } - - public boolean hasTag(String tag) { - for (String myTag : tags) { - if (myTag.equals(tag)) { - return true; - } - } - return false; - } -} - - diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Speaker.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Speaker.java deleted file mode 100644 index 0bd59c0ecc..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Speaker.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -import com.google.samples.apps.iosched.util.HashUtils; - -public class Speaker { - public String id; - public String publicPlusId; - public String bio; - public String name; - public String company; - public String plusoneUrl; - public String thumbnailUrl; - - public String getImportHashcode() { - StringBuilder sb = new StringBuilder(); - sb.append("id").append(id == null ? "" : id) - .append("publicPlusId").append(publicPlusId == null ? "" : publicPlusId) - .append("bio").append(bio == null ? "" : bio) - .append("name").append(name == null ? "" : name) - .append("company").append(company== null ? "" : company) - .append("plusoneUrl").append(plusoneUrl == null ? "" : plusoneUrl) - .append("thumbnailUrl").append(thumbnailUrl == null ? "" : thumbnailUrl); - return HashUtils.computeWeakHash(sb.toString()); - } -} - - diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Tag.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Tag.java deleted file mode 100644 index 29e28934fd..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Tag.java +++ /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. - */ - -package com.google.samples.apps.iosched.io.model; - -import com.google.gson.annotations.SerializedName; - -public class Tag { - public String tag; - public String name; - public String category; - public String color; - @SerializedName("abstract") - public String _abstract; - public int order_in_category; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/io/model/Video.java b/android/src/main/java/com/google/samples/apps/iosched/io/model/Video.java deleted file mode 100644 index 7c3f59dab3..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/io/model/Video.java +++ /dev/null @@ -1,44 +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 com.google.samples.apps.iosched.io.model; - -import com.google.samples.apps.iosched.util.HashUtils; - -public class Video { - public String id; - public int year; - public String title; - public String desc; - public String vid; - public String topic; - public String speakers; - public String thumbnailUrl; - - public String getImportHashcode() { - StringBuilder sb = new StringBuilder(); - sb.append("id").append(id == null ? "" : id) - .append("year").append(year) - .append("title").append(title == null ? "" : title) - .append("desc").append(desc == null ? "" : desc) - .append("vid").append(vid == null ? "" : vid) - .append("topic").append(topic == null ? "" : topic) - .append("speakers").append(speakers == null ? "" : speakers) - .append("thumbnailUrl").append(thumbnailUrl == null ? "" : thumbnailUrl); - return HashUtils.computeWeakHash(sb.toString()); - } -} - diff --git a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleHelper.java b/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleHelper.java deleted file mode 100644 index 09f6c98c61..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleHelper.java +++ /dev/null @@ -1,279 +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 com.google.samples.apps.iosched.model; - -import android.content.Context; -import android.database.Cursor; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.util.Log; - -import com.google.samples.apps.iosched.BuildConfig; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.provider.ScheduleContract.Blocks; -import com.google.samples.apps.iosched.provider.ScheduleContract.Sessions; -import com.google.samples.apps.iosched.ui.MyScheduleAdapter; -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.UIUtils; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class ScheduleHelper { - - private static final String TAG = makeLogTag(ScheduleHelper.class); - - private Context mContext; - - public ScheduleHelper(Context context) { - this.mContext = context; - } - - public ArrayList getScheduleData(long start, long end) { - // get sessions in my schedule and blocks, starting anytime in the conference day - ArrayList mutableItems = new ArrayList(); - ArrayList immutableItems = new ArrayList(); - addBlocks(start, end, mutableItems, immutableItems); - addSessions(start, end, mutableItems, immutableItems); - - ArrayList result = ScheduleItemHelper.processItems(mutableItems, immutableItems); - if (BuildConfig.DEBUG || Log.isLoggable(TAG, Log.DEBUG)) { - ScheduleItem previous = null; - for (ScheduleItem item: result) { - if ((item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_PREVIOUS) != 0) { - Log.d(TAG, "Schedule Item conflicts with previous. item="+item+" previous="+previous); - } - previous = item; - } - } - - setSessionCounters(result, start, end); - return result; - } - - /** - * Fill the number of sessions for FREE blocks: - */ - protected void setSessionCounters(ArrayList items, long dayStart, long dayEnd) { - ArrayList free = new ArrayList(); - - for (ScheduleItem item: items) { - if (item.type == ScheduleItem.FREE) { - free.add(item); - } - } - - if (free.isEmpty()) { - return; - } - - // Count number of start/end pairs for sessions that are between dayStart and dayEnd and - // are not in my schedule: - String liveStreamedOnlySelection = UIUtils.shouldShowLiveSessionsOnly(mContext) - ? "AND IFNULL(" + ScheduleContract.Sessions.SESSION_LIVESTREAM_URL + ",'')!=''" - : ""; - Cursor cursor = mContext.getContentResolver().query( - ScheduleContract.Sessions.buildCounterByIntervalUri(), - SessionsCounterQuery.PROJECTION, - Sessions.SESSION_START + ">=? AND "+Sessions.SESSION_START + "<=? AND "+ - Sessions.SESSION_IN_MY_SCHEDULE + " = 0 "+liveStreamedOnlySelection, - new String[]{String.valueOf(dayStart), String.valueOf(dayEnd)}, - null); - - while (cursor.moveToNext()) { - long start = cursor.getLong(SessionsCounterQuery.SESSION_INTERVAL_START); - int counter = cursor.getInt(SessionsCounterQuery.SESSION_INTERVAL_COUNT); - - // Find blocks that this interval applies. - for (ScheduleItem item: free) { - // If grouped sessions starts and ends inside the free block, it is considered in it: - if (item.startTime <= start && start < item.endTime) { - item.numOfSessions += counter; - } - } - } - cursor.close(); - - // remove free blocks that have no available sessions or that are in the past - long now = UIUtils.getCurrentTime(mContext); - Iterator it = items.iterator(); - while (it.hasNext()) { - ScheduleItem i = it.next(); - if (i.type == ScheduleItem.FREE) { - if (i.endTime < now) { - LOGD(TAG, "Removing empty block in the past."); - it.remove(); - } else if (i.numOfSessions == 0) { - LOGD(TAG, "Removing block with zero sessions: " + new Date(i.startTime) + "-" + new Date(i.endTime)); - it.remove(); - } else { - i.subtitle = mContext.getResources().getQuantityString( - R.plurals.schedule_block_subtitle, i.numOfSessions, i.numOfSessions); - } - - } - } - } - - public void getScheduleDataAsync(final MyScheduleAdapter adapter, - long start, long end) { - new AsyncTask>() { - @Override - protected ArrayList doInBackground(Long... params) { - Long start = params[0]; - Long end = params[1]; - return getScheduleData(start, end); - } - - @Override - protected void onPostExecute(ArrayList scheduleItems) { - adapter.updateItems(scheduleItems); - } - }.execute(start, end); - } - - protected void addSessions(long start, long end, - ArrayList mutableItems, ArrayList immutableItems) { - - Cursor cursor = mContext.getContentResolver().query( - ScheduleContract.addOverrideAccountName(Sessions.CONTENT_MY_SCHEDULE_URI, AccountUtils.getActiveAccountName(mContext)), - SessionsQuery.PROJECTION, - // filter sessions to the specified day - Sessions.STARTING_AT_TIME_INTERVAL_SELECTION, - new String[]{String.valueOf(start), String.valueOf(end)}, - // order by session start - Sessions.SESSION_START); - - while (cursor.moveToNext()) { - ScheduleItem item = new ScheduleItem(); - item.type = ScheduleItem.SESSION; - item.sessionId = cursor.getString(SessionsQuery.SESSION_ID); - item.title = cursor.getString(SessionsQuery.SESSION_TITLE); - item.startTime = cursor.getLong(SessionsQuery.SESSION_START); - item.endTime = cursor.getLong(SessionsQuery.SESSION_END); - if (!TextUtils.isEmpty(cursor.getString(SessionsQuery.SESSION_LIVESTREAM_URL))) { - item.flags |= ScheduleItem.FLAG_HAS_LIVESTREAM; - } - item.subtitle = UIUtils.formatSessionSubtitle( - cursor.getString(SessionsQuery.ROOM_ROOM_NAME), - cursor.getString(SessionsQuery.SESSION_SPEAKER_NAMES), mContext); - item.backgroundImageUrl = cursor.getString(SessionsQuery.SESSION_PHOTO_URL); - item.backgroundColor = cursor.getInt(SessionsQuery.SESSION_COLOR); - item.hasGivenFeedback = (cursor.getInt(SessionsQuery.HAS_GIVEN_FEEDBACK) > 0); - immutableItems.add(item); - } - cursor.close();; - } - - protected void addBlocks(long start, long end, - ArrayList mutableItems, ArrayList immutableItems) { - Cursor cursor = mContext.getContentResolver().query( - Blocks.CONTENT_URI, - BlocksQuery.PROJECTION, - - // filter sessions on the specified day - Blocks.BLOCK_START + " >= ? and " + Blocks.BLOCK_START + " <= ?", - new String[]{String.valueOf(start), String.valueOf(end)}, - - // order by session start - Blocks.BLOCK_START); - - while (cursor.moveToNext()) { - ScheduleItem item = new ScheduleItem(); - item.setTypeFromBlockType(cursor.getString(BlocksQuery.BLOCK_TYPE)); - item.title = cursor.getString(BlocksQuery.BLOCK_TITLE); - item.subtitle = cursor.getString(BlocksQuery.BLOCK_SUBTITLE); - item.startTime = cursor.getLong(BlocksQuery.BLOCK_START); - item.endTime = cursor.getLong(BlocksQuery.BLOCK_END); - - // Hide BREAK blocks to remote attendees (b/14666391): - if (item.type == ScheduleItem.BREAK && !PrefUtils.isAttendeeAtVenue(mContext)) { - continue; - } - // Currently, only type=FREE is mutable - if (item.type == ScheduleItem.FREE) { - mutableItems.add(item); - } else { - immutableItems.add(item); - item.flags |= ScheduleItem.FLAG_NOT_REMOVABLE; - } - } - } - - private interface SessionsQuery { - String[] PROJECTION = { - Sessions.SESSION_ID, - Sessions.SESSION_TITLE, - Sessions.SESSION_START, - Sessions.SESSION_END, - ScheduleContract.Rooms.ROOM_NAME, - Sessions.SESSION_IN_MY_SCHEDULE, - Sessions.SESSION_LIVESTREAM_URL, - Sessions.SESSION_SPEAKER_NAMES, - Sessions.SESSION_PHOTO_URL, - Sessions.SESSION_COLOR, - Sessions.HAS_GIVEN_FEEDBACK, - }; - - int SESSION_ID = 0; - int SESSION_TITLE = 1; - int SESSION_START = 2; - int SESSION_END = 3; - int ROOM_ROOM_NAME = 4; - int SESSION_LIVESTREAM_URL = 6; - int SESSION_SPEAKER_NAMES = 7; - int SESSION_PHOTO_URL = 8; - int SESSION_COLOR = 9; - int HAS_GIVEN_FEEDBACK = 10; - } - - private interface BlocksQuery { - String[] PROJECTION = { - Blocks.BLOCK_TITLE, - Blocks.BLOCK_TYPE, - Blocks.BLOCK_START, - Blocks.BLOCK_END, - Blocks.BLOCK_SUBTITLE - }; - - int BLOCK_TITLE = 0; - int BLOCK_TYPE= 1; - int BLOCK_START = 2; - int BLOCK_END = 3; - int BLOCK_SUBTITLE = 4; - } - - - private interface SessionsCounterQuery { - String[] PROJECTION = { - Sessions.SESSION_START, - Sessions.SESSION_END, - Sessions.SESSION_INTERVAL_COUNT, - Sessions.SESSION_IN_MY_SCHEDULE, - }; - - int SESSION_INTERVAL_START = 0; - int SESSION_INTERVAL_END = 1; - int SESSION_INTERVAL_COUNT = 2; - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItem.java b/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItem.java deleted file mode 100644 index c0095ee5c3..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItem.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 com.google.samples.apps.iosched.model; - -import com.google.samples.apps.iosched.provider.ScheduleContract; - -public class ScheduleItem implements Cloneable, Comparable { - // types: - public static final int FREE = 0; // a free chunk of time - public static final int SESSION = 1; // a session - public static final int BREAK = 2; // a break (lunch, breaks, after-hours party) - - // item type - public int type = FREE; - - // start and end time for this item - public long startTime = 0; - public long endTime = 0; - - // number of sessions available in this block (usually for free blocks) - public int numOfSessions = 0; - - // session id - public String sessionId = ""; - - // title and subtitle - public String title = ""; - public String subtitle = ""; - - // has feedback been given on this session? - public boolean hasGivenFeedback; - - // background image URL - public String backgroundImageUrl = ""; - public int backgroundColor = 0; - - // flags - public int flags = 0; - public static final int FLAG_HAS_LIVESTREAM = 0x01; - public static final int FLAG_NOT_REMOVABLE = 0x02; - public static final int FLAG_CONFLICTS_WITH_PREVIOUS = 0x04; - - public void setTypeFromBlockType(String blockType) { - if (!ScheduleContract.Blocks.isValidBlockType(blockType) || - ScheduleContract.Blocks.BLOCK_TYPE_FREE.equals(blockType)) { - type = FREE; - } else { - type = BREAK; - } - } - - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException unused) { - // does not happen (since we implement Cloneable) - return new ScheduleItem(); - } - } - - @Override - public int compareTo(ScheduleItem another) { - return this.startTime < another.startTime ? -1 : - ( this.startTime > another.startTime ? 1 : 0 ); - } - - @Override - public boolean equals(Object o) { - if (o == null || !(o instanceof ScheduleItem)) { - return false; - } - ScheduleItem i = (ScheduleItem) o; - return type == i.type && - sessionId.equals(i.sessionId) && - startTime == i.startTime && - endTime == i.endTime; - } - - @Override - public String toString() { - return String.format("[item type=%d, startTime=%d, endTime=%d, title=%s, subtitle=%s, flags=%d]", - type, startTime, endTime, title, subtitle, flags); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItemHelper.java b/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItemHelper.java deleted file mode 100644 index c4c4da2338..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/model/ScheduleItemHelper.java +++ /dev/null @@ -1,132 +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 com.google.samples.apps.iosched.model; - -import java.util.*; - -public class ScheduleItemHelper { - - private static final long FREE_BLOCK_MINIMUM_LENGTH = 10 * 60 * 1000; // 10 minutes - public static final long ALLOWED_OVERLAP = 5 * 60 * 1000; // 5 minutes - - /** - * Find and resolve time slot conflicts. - * Items should already be ordered by start time. Conflicts among mutableItems, if any, - * won't be checked, and they will be left as is. - **/ - static public ArrayList processItems(ArrayList mutableItems, ArrayList immutableItems) { - - // move mutables as necessary to accommodate conflicts with immutables: - moveMutables(mutableItems, immutableItems); - - // mark conflicting immutable: - markConflicting(immutableItems); - - ArrayList result = new ArrayList(); - result.addAll(immutableItems); - result.addAll(mutableItems); - - Collections.sort(result, new Comparator() { - @Override - public int compare(ScheduleItem lhs, ScheduleItem rhs) { - return lhs.startTime < rhs.startTime ? -1 : 1; - } - }); - - return result; - } - - static protected void markConflicting(ArrayList items) { - for (int i=0; i mutableItems, ArrayList immutableItems) { - Iterator immutableIt = immutableItems.iterator(); - - while (immutableIt.hasNext()) { - ScheduleItem immutableItem = immutableIt.next(); - if (immutableItem.type == ScheduleItem.BREAK) { - // Breaks (lunch, after hours, etc) should not make free blocks to move - continue; - } - ListIterator mutableIt = mutableItems.listIterator(); - while (mutableIt.hasNext()) { - ScheduleItem mutableItem = mutableIt.next(); - ScheduleItem split = null; - - // If mutable item is overlapping the immutable one - if (intersect(immutableItem, mutableItem, true)) { - if (isContainedInto(mutableItem, immutableItem)) { - // if mutable is entirely contained into immutable, just remove it - mutableIt.remove(); - continue; - } else if (isContainedInto(immutableItem, mutableItem)) { - // if immutable is entirely contained into mutable, split mutable if necessary: - if (isIntervalLongEnough(immutableItem.endTime, mutableItem.endTime)) { - split = (ScheduleItem) mutableItem.clone(); - split.startTime = immutableItem.endTime; - } - mutableItem.endTime = immutableItem.startTime; - } else if (mutableItem.startTime < immutableItem.endTime) { - // Adjust the start of the mutable - mutableItem.startTime = immutableItem.endTime; - } else if (mutableItem.endTime > immutableItem.startTime) { - // Adjust the end of the mutable - mutableItem.endTime = immutableItem.startTime; - } - - if (!isIntervalLongEnough(mutableItem.startTime, mutableItem.endTime)) { - mutableIt.remove(); - } - if (split != null) { - mutableIt.add(split); - } - } - } - } - - } - - static private boolean isIntervalLongEnough(long start, long end) { - return ( end - start ) >= FREE_BLOCK_MINIMUM_LENGTH; - } - static private boolean intersect(ScheduleItem block1, ScheduleItem block2, boolean useOverlap) { - return block2.endTime > ( block1.startTime + ( useOverlap ? ALLOWED_OVERLAP : 0 ) ) - && ( block2.startTime + ( useOverlap ?ALLOWED_OVERLAP : 0 ) ) < block1.endTime; - } - - static private boolean isContainedInto(ScheduleItem contained, ScheduleItem container) { - return contained.startTime >= container.startTime && - contained.endTime <= container.endTime; - } - - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/model/TagMetadata.java b/android/src/main/java/com/google/samples/apps/iosched/model/TagMetadata.java deleted file mode 100644 index 1f5838a9df..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/model/TagMetadata.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 com.google.samples.apps.iosched.model; - -import android.content.Context; -import android.content.CursorLoader; -import android.database.Cursor; -import android.provider.BaseColumns; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.provider.ScheduleContract; - -import java.util.*; - -public class TagMetadata { - - // list of tags in each category, sorted by the category sort order - HashMap> mTagsInCategory = new HashMap>(); - - // hash map from tag ID to tag - HashMap mTagsById = new HashMap(); - - public static CursorLoader createCursorLoader(Context context) { - return new CursorLoader(context, ScheduleContract.Tags.CONTENT_URI, TagsQuery.PROJECTION, - null, null, null); - } - - public TagMetadata(Cursor cursor) { - while (cursor.moveToNext()) { - Tag tag = new Tag(cursor.getString(TagsQuery.TAG_ID), - cursor.getString(TagsQuery.TAG_NAME), - cursor.getString(TagsQuery.TAG_CATEGORY), - cursor.getInt(TagsQuery.TAG_ORDER_IN_CATEGORY), - cursor.getString(TagsQuery.TAG_ABSTRACT), - cursor.getInt(TagsQuery.TAG_COLOR)); - mTagsById.put(tag.getId(), tag); - if (!mTagsInCategory.containsKey(tag.getCategory())) { - mTagsInCategory.put(tag.getCategory(), new ArrayList()); - } - mTagsInCategory.get(tag.getCategory()).add(tag); - } - - for (ArrayList list : mTagsInCategory.values()) { - Collections.sort(list); - } - } - - public Tag getTag(String tagId) { - return mTagsById.containsKey(tagId) ? mTagsById.get(tagId) : null; - } - - public List getTagsInCategory(String category) { - return mTagsInCategory.containsKey(category) ? - Collections.unmodifiableList(mTagsInCategory.get(category)) : null; - } - - /** Given the set of tags on a session, returns its group label. */ - public Tag getSessionGroupTag(String[] sessionTags) { - int bestOrder = Integer.MAX_VALUE; - Tag bestTag = null; - for (String tagId : sessionTags) { - Tag tag = getTag(tagId); - if (tag != null && Config.Tags.SESSION_GROUPING_TAG_CATEGORY.equals(tag.getCategory()) && - tag.getOrderInCategory() < bestOrder) { - bestOrder = tag.getOrderInCategory(); - bestTag = tag; - } - } - return bestTag; - } - - public static Comparator TAG_DISPLAY_ORDER_COMPARATOR = new Comparator() { - @Override - public int compare(Tag tag, Tag tag2) { - if (!TextUtils.equals(tag.getCategory(), tag2.getCategory())) { - return Config.Tags.CATEGORY_DISPLAY_ORDERS.get(tag.getCategory()) - - Config.Tags.CATEGORY_DISPLAY_ORDERS.get(tag2.getCategory()); - } else if (tag.getOrderInCategory() != tag2.getOrderInCategory()) { - return tag.getOrderInCategory() - tag2.getOrderInCategory(); - } - - return tag.getName().compareTo(tag2.getName()); - } - }; - - private interface TagsQuery { - int _TOKEN = 0x1; - - String[] PROJECTION = { - BaseColumns._ID, - ScheduleContract.Tags.TAG_ID, - ScheduleContract.Tags.TAG_NAME, - ScheduleContract.Tags.TAG_CATEGORY, - ScheduleContract.Tags.TAG_ORDER_IN_CATEGORY, - ScheduleContract.Tags.TAG_ABSTRACT, - ScheduleContract.Tags.TAG_COLOR - }; - - int _ID = 0; - int TAG_ID = 1; - int TAG_NAME = 2; - int TAG_CATEGORY = 3; - int TAG_ORDER_IN_CATEGORY = 4; - int TAG_ABSTRACT = 5; - int TAG_COLOR = 6; - } - - static public class Tag implements Comparable { - private String mId; - private String mName; - private String mCategory; - private int mOrderInCategory; - private String mAbstract; - private int mColor; - - public Tag(String id, String name, String category, int orderInCategory, String _abstract, - int color) { - mId = id; - mName = name; - mCategory = category; - mOrderInCategory = orderInCategory; - mAbstract = _abstract; - mColor = color; - } - - public String getId() { - return mId; - } - - public String getName() { - return mName; - } - - public String getCategory() { - return mCategory; - } - - public int getOrderInCategory() { - return mOrderInCategory; - } - - public String getAbstract() { - return mAbstract; - } - - public int getColor() { - return mColor; - } - - @Override - public int compareTo(Tag another) { - return mOrderInCategory - another.mOrderInCategory; - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/nearby/DeviceMetadata.java b/android/src/main/java/com/google/samples/apps/iosched/nearby/DeviceMetadata.java deleted file mode 100644 index e33419b836..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/nearby/DeviceMetadata.java +++ /dev/null @@ -1,30 +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 com.google.samples.apps.iosched.nearby; - -import android.graphics.Bitmap; - -/** - * Container class for metadata pertaining to BLE devices. - */ -public class DeviceMetadata { - public String title; - public String siteUrl; - public String description; - public String iconUrl; - public Bitmap icon; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/nearby/MetadataResolver.java b/android/src/main/java/com/google/samples/apps/iosched/nearby/MetadataResolver.java deleted file mode 100644 index f1abd3cbc2..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/nearby/MetadataResolver.java +++ /dev/null @@ -1,225 +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 com.google.samples.apps.iosched.nearby; - -import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; -import android.util.Log; -import android.util.Patterns; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -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 static com.google.samples.apps.iosched.Config.METADATA_URL; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Class for resolving BLE device metadata via proxy. - */ -public class MetadataResolver { - static String TAG = makeLogTag("MetadataResolver"); - - static Map mDeviceUrlMap; - static RequestQueue mRequestQueue; - - static boolean mIsInitialized = false; - - public static void initialize(Context context) { - mDeviceUrlMap = new HashMap(); - if (mRequestQueue == null) { - mRequestQueue = Volley.newRequestQueue(context); - } - - mIsInitialized = true; - } - - public static String getURLForDevice(NearbyDevice device) { - if (!mIsInitialized) { - Log.e(TAG, "Not initialized."); - return null; - } - - // If the device name is already a URL, use it. - String deviceName = device.getName(); - String url = deviceName; - if (Patterns.WEB_URL.matcher(deviceName).matches()) { - // TODO: Improve this. - // For now, if there's no scheme present, add a default http:// scheme. - if (!url.startsWith("http://") && !url.startsWith("https://")) { - url = "http://" + url; - } - } else { - // Otherwise, try doing the lookup. - url = mDeviceUrlMap.get(deviceName); - } - return url; - } - - public static void getBatchMetadata(List batchList) { - if (!mIsInitialized) { - Log.e(TAG, "Not initialized."); - return; - } - - JSONObject jsonObj = createRequestObject(batchList); - - Map deviceMap = new HashMap(); - - for (int i = 0; i < batchList.size(); i++) { - NearbyDevice nearbyDevice = batchList.get(i); - deviceMap.put(nearbyDevice.getUrl(), nearbyDevice); - } - - JsonObjectRequest jsObjRequest = createMetadataRequest(jsonObj, deviceMap); - - // Queue the request - mRequestQueue.add(jsObjRequest); - } - - private static JsonObjectRequest createMetadataRequest(JSONObject jsonObj, final Map deviceMap) { - return new JsonObjectRequest( - METADATA_URL, - jsonObj, - new Response.Listener() { - @Override - public void onResponse(JSONObject jsonResponse) { - - try { - JSONArray foundMetaData = jsonResponse.getJSONArray("metadata"); - - int deviceCount = foundMetaData.length(); - for (int i = 0; i < deviceCount; i++) { - - JSONObject deviceData = foundMetaData.getJSONObject(i); - - String title = "Unknown name"; - String url = "Unknown url"; - String description = "Unknown description"; - String iconUrl = "/favicon.ico"; - String id = deviceData.getString("id"); - - if (deviceData.has("title")) { - title = deviceData.getString("title"); - } - if (deviceData.has("url")) { - url = deviceData.getString("url"); - } - if (deviceData.has("description")) { - description = deviceData.getString("description"); - } - if (deviceData.has("icon")) { - // We might need to do some magic here. - iconUrl = deviceData.getString("icon"); - } - // Provisions for a favicon specified as a relative URL. - if (!iconUrl.startsWith("http")) { - // Lets just assume we are dealing with a relative path. - Uri fullUri = Uri.parse(url); - Uri.Builder builder = fullUri.buildUpon(); - // Append the default favicon path to the URL. - builder.path(iconUrl); - iconUrl = builder.toString(); - } - - DeviceMetadata deviceMetadata = new DeviceMetadata(); - deviceMetadata.title = title; - deviceMetadata.description = description; - deviceMetadata.siteUrl = url; - deviceMetadata.iconUrl = iconUrl; - downloadIcon(deviceMetadata, deviceMap.get(id)); - - // Look up the device from the input and update the data - deviceMap.get(id).onDeviceInfo(deviceMetadata); - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError volleyError) { - Log.i(TAG, "VolleyError: " + volleyError.toString()); - } - } - ); - } - - private static JSONObject createRequestObject(List devices) { - JSONObject jsonObj = new JSONObject(); - - try { - JSONArray urlArray = new JSONArray(); - - for(int dIdx = 0; dIdx < devices.size(); dIdx++) { - NearbyDevice device = devices.get(dIdx); - - JSONObject urlObject = new JSONObject(); - - urlObject.put("url", device.getUrl()); - urlObject.put("rssi", device.getLastRSSI()); - urlArray.put(urlObject); - } - - - JSONObject location = new JSONObject(); - - location.put("lat", 49.129837); - location.put("lon", 120.38142); - - jsonObj.put("location", location); - jsonObj.put("objects", urlArray); - - } catch(JSONException ex) { - - } - return jsonObj; - } - - /** - * Asynchronously download the image for the nearby device. - * @param metadata - * @param listener - */ - private static void downloadIcon(final DeviceMetadata metadata, final OnMetadataListener listener) { - ImageRequest imageRequest = new ImageRequest(metadata.iconUrl, new Response.Listener() { - @Override - public void onResponse(Bitmap response) { - Log.i(TAG, "Got an image: " + response.toString()); - metadata.icon = response; - listener.onDeviceInfo(metadata); - } - }, 0, 0, null, null); - mRequestQueue.add(imageRequest); - } - - public interface OnMetadataListener { - public void onDeviceInfo(DeviceMetadata deviceMetadata); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDevice.java b/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDevice.java deleted file mode 100644 index b752502411..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDevice.java +++ /dev/null @@ -1,119 +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 com.google.samples.apps.iosched.nearby; - -import android.bluetooth.BluetoothDevice; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Represents a nearby device emitting a BLE signal. - */ -public class NearbyDevice implements MetadataResolver.OnMetadataListener { - String TAG = makeLogTag("NearbyDevice"); - private BluetoothDevice mBluetoothDevice; - private DeviceMetadata mDeviceMetadata; - private String mUrl; - private NearbyDeviceAdapter mAdapter; - - private int HISTORY_LENGTH = 3; - private List mRSSIHistory; - private long mLastSeen; - - NearbyDevice(BluetoothDevice bluetoothDevice, int RSSI) { - mBluetoothDevice = bluetoothDevice; - String url = MetadataResolver.getURLForDevice(this); - initialize(url, RSSI); - } - - private void initialize(String url, int RSSI) { - mUrl = url; - mLastSeen = System.nanoTime(); - - mRSSIHistory = new ArrayList(); - mRSSIHistory.add(RSSI); - } - - public void setAdapter(NearbyDeviceAdapter adapter) { - mAdapter = adapter; - } - - public DeviceMetadata getInfo() { - return mDeviceMetadata; - } - - public int getLastRSSI() { - return mRSSIHistory.get(mRSSIHistory.size() - 1); - } - - public int getAverageRSSI() { - Log.i(TAG, "getAverageRSSI. Elements: " + mRSSIHistory.size()); - int sum = 0; - for (int rssi : mRSSIHistory) { - sum += rssi; - } - return sum/mRSSIHistory.size(); - } - - public String getUrl() { - return mUrl; - } - - public String getName() { - if (mBluetoothDevice != null) { - String name = mBluetoothDevice.getName(); - if (name != null) { - return name; - } else { - return "No device name"; - } - } else { - return mUrl; - } - } - - public void updateLastSeen(int RSSI) { - mLastSeen = System.nanoTime(); - - if (mRSSIHistory.size() >= HISTORY_LENGTH) { - mRSSIHistory.remove(0); - } - mRSSIHistory.add(RSSI); - } - - public boolean isLastSeenAfter(long threshold) { - long notSeenMs = (System.nanoTime() - mLastSeen) / 1000000; - return notSeenMs > threshold; - } - - @Override - public void onDeviceInfo(DeviceMetadata deviceMetadata) { - mDeviceMetadata = deviceMetadata; - if (mAdapter != null) { - mAdapter.updateListUI(); - } - } - - public boolean isBroadcastingUrl() { - return mUrl != null; - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceAdapter.java b/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceAdapter.java deleted file mode 100644 index 0edca45205..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceAdapter.java +++ /dev/null @@ -1,216 +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 com.google.samples.apps.iosched.nearby; - -import android.app.Activity; -import android.os.Handler; -import android.text.Html; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.google.samples.apps.iosched.R; - -import java.util.*; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Adapter class for building views related to BLE devices. - */ -public class NearbyDeviceAdapter extends BaseAdapter { - String TAG = makeLogTag("NearbyDeviceAdapter"); - - private ArrayList mNearbyDevices; - private Activity mActivity; - - private Handler mHandler = new Handler(); - - private long mLastChangeRequestTime = 0; - private long NOTIFY_DELAY = 300; - - NearbyDeviceAdapter(Activity activity) { - mNearbyDevices = new ArrayList(); - mActivity = activity; - } - @Override - public int getCount() { - return mNearbyDevices.size(); - } - - @Override - public Object getItem(int i) { - return mNearbyDevices.get(i); - } - - @Override - public long getItemId(int i) { - NearbyDevice device = mNearbyDevices.get(i); - return System.identityHashCode(device); - } - - @Override - public View getView(int i, View view, ViewGroup container) { - if (view == null) { - view = mActivity.getLayoutInflater().inflate(R.layout.ble_listitem_device, null); - } - - NearbyDevice device = mNearbyDevices.get(i); - - DeviceMetadata deviceMetadata = device.getInfo(); - TextView infoView; - - infoView = (TextView) view.findViewById(R.id.title); - if (deviceMetadata != null) { - String title = deviceMetadata.title; - if (!TextUtils.isEmpty(title)) { - infoView.setText(Html.fromHtml(deviceMetadata.title)); - infoView.setVisibility(View.VISIBLE); - } else { - infoView.setVisibility(View.GONE); - } - } else { - infoView.setText(R.string.loading); - infoView.setVisibility(View.VISIBLE); - } - - infoView = (TextView) view.findViewById(R.id.url); - if (deviceMetadata != null) { - infoView.setText(deviceMetadata.siteUrl); - } else { - infoView.setText(device.getUrl()); - } - - infoView = (TextView) view.findViewById(R.id.description); - if (deviceMetadata != null) { - String description = deviceMetadata.description; - if (!TextUtils.isEmpty(description)) { - infoView.setText(Html.fromHtml(deviceMetadata.description)); - infoView.setVisibility(View.VISIBLE); - } else { - infoView.setVisibility(View.GONE); - } - } else { - infoView.setVisibility(View.INVISIBLE); - } - - ImageView iconView = (ImageView) view.findViewById(R.id.icon); - if (deviceMetadata != null) { - iconView.setImageBitmap(deviceMetadata.icon); - } else { - iconView.setImageResource(R.drawable.empty_nearby_icon); - } - - return view; - } - - public void addDevice(final NearbyDevice device) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mNearbyDevices.add(device); - device.setAdapter(NearbyDeviceAdapter.this); - queueChangedNotification(); - } - }); - } - - public NearbyDevice getExistingDevice(NearbyDevice candidateDevice) { - for (NearbyDevice device : mNearbyDevices) { - if (device.getUrl().equals(candidateDevice.getUrl())) { - return device; - } - } - return null; - } - - public ArrayList removeExpiredDevices() { - // Get a list of devices that we need to remove. - ArrayList toRemove = new ArrayList(); - for (NearbyDevice device : mNearbyDevices) { - if (device.isLastSeenAfter(NearbyDeviceManager.MAX_INACTIVE_TIME)) { - toRemove.add(device); - } - } - - // Remove those devices from the list and notify the listener. - for (final NearbyDevice device : toRemove) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mNearbyDevices.remove(device); - queueChangedNotification(); - } - }); - } - return toRemove; - } - - public void updateListUI() { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - queueChangedNotification(); - } - }); - } - - private Runnable mNotifyRunnable = new Runnable() { - @Override - public void run() { - notifyDataSetChanged(); - } - }; - - public void queueChangedNotification() { - long now = System.currentTimeMillis(); - // If a notification was recently issued, create a pending notification. - if (now - mLastChangeRequestTime < NOTIFY_DELAY) { - // Ignore if there's a pending timer already. - mHandler.removeCallbacks(mNotifyRunnable); - mHandler.postDelayed(mNotifyRunnable, NOTIFY_DELAY); - } else { - // Otherwise, if there's no active timer, notify immediately. - Log.i(TAG, "queueChangedNotification: Immediately notifying."); - notifyDataSetChanged(); - } - } - - @Override - public void notifyDataSetChanged() { - Log.i(TAG, "queueChangedNotification: notifyDataSetChanged"); - Collections.sort(mNearbyDevices, mRssiComparator); - - super.notifyDataSetChanged(); - - // Cancel the pending notification timer if there is one. - mHandler.removeCallbacks(mNotifyRunnable); - mLastChangeRequestTime = System.currentTimeMillis(); - } - - - private Comparator mRssiComparator = new Comparator() { - @Override - public int compare(NearbyDevice lhs, NearbyDevice rhs) { - return rhs.getAverageRSSI() - lhs.getAverageRSSI(); - } - }; -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceManager.java b/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceManager.java deleted file mode 100644 index 62520e0c19..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/nearby/NearbyDeviceManager.java +++ /dev/null @@ -1,190 +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 com.google.samples.apps.iosched.nearby; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Build; -import android.os.Handler; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Keeps track of all devices nearby. - * - * Posts notifications when a new device is near, or if an old device is not - * longer nearby. - */ -@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) -public class NearbyDeviceManager { - private String TAG = makeLogTag("NearbyDeviceManager"); - - private BluetoothAdapter mBluetoothAdapter; - private Timer mExpireTimer; - private Timer mSearchTimer; - private Handler mQueryHandler; - private boolean mIsSearching = false; - - private NearbyDeviceAdapter mNearbyDeviceAdapter; - - private ArrayList mDeviceBatchList; - - private Activity mActivity; - - private boolean mIsQueuing = false; - // How often we should batch requests for metadata. - private int QUERY_PERIOD = 500; - // How often to search for new devices (ms). - private int SEARCH_PERIOD = 5000; - // How often to check for expired devices. - private int EXPIRE_PERIOD = 3000; - // How much time has to pass with a nearby device not being discovered before - // we declare it gone. - public static int MAX_INACTIVE_TIME = 10000; - - public NearbyDeviceManager(Activity activity, BluetoothAdapter adapter) { - mBluetoothAdapter = adapter; - mDeviceBatchList = new ArrayList(); - mNearbyDeviceAdapter = new NearbyDeviceAdapter(activity); - mQueryHandler = new Handler(); - mActivity = activity; - } - - public boolean isEnabled() { - return mBluetoothAdapter.isEnabled(); - } - - // Begins searching for nearby devices if not already searching. If the adapter is disabled, - // does nothing. - public void startSearchingForDevices() { - if (mIsSearching || !mBluetoothAdapter.isEnabled()) { - return; - } - mIsSearching = true; - - // Start a timer to do scans. - if (mSearchTimer == null) { - mSearchTimer = new Timer(); - mSearchTimer.scheduleAtFixedRate(new SearchTask(), 0, SEARCH_PERIOD); - } - - // Start a timer to check for expired devices. - if (mExpireTimer == null) { - mExpireTimer = new Timer(); - mExpireTimer.scheduleAtFixedRate(new ExpireTask(), 0, EXPIRE_PERIOD); - } - } - - public void stopSearchingForDevices() { - if (!mIsSearching) { - return; - } - mIsSearching = false; - mBluetoothAdapter.stopLeScan(mLeScanCallback); - - // Stop expired device timer. - mExpireTimer.cancel(); - mExpireTimer.purge(); - mExpireTimer = null; - mSearchTimer.cancel(); - mSearchTimer.purge(); - mSearchTimer = null; - } - - public NearbyDeviceAdapter getAdapter() { - return mNearbyDeviceAdapter; - } - - private class SearchTask extends TimerTask { - - @Override - public void run() { - mBluetoothAdapter.stopLeScan(mLeScanCallback); - boolean result = mBluetoothAdapter.startLeScan(mLeScanCallback); - if (!result) { - Log.e(TAG, "startLeScan failed."); - } - } - } - - private class ExpireTask extends TimerTask { - @Override - public void run() { - mNearbyDeviceAdapter.removeExpiredDevices(); - } - } - - private Runnable mBatchMetadataRunnable = new Runnable () { - @Override - public void run() { - batchFetchMetaData(); - mIsQueuing = false; - } - }; - - private void batchFetchMetaData() { - if(mDeviceBatchList.size() > 0) { - MetadataResolver.getBatchMetadata(mDeviceBatchList); - mDeviceBatchList = new ArrayList(); // Clear out the list - } - } - - // NearbyDevice scan callback. - private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { - @Override - public void onLeScan(final BluetoothDevice device, final int RSSI, byte[] scanRecord) { - Log.i(TAG, String.format("onLeScan: %s, RSSI: %d", device.getName(), RSSI)); - - if (device.getName() == null) { - return; - } - NearbyDevice candidateNearbyDevice = new NearbyDevice(device, RSSI); - handleDeviceFound(candidateNearbyDevice); - } - }; - - private void handleDeviceFound(NearbyDevice candidateNearbyDevice) { - NearbyDevice nearbyDevice = mNearbyDeviceAdapter.getExistingDevice(candidateNearbyDevice); - - // Check if this is a new device. - if (nearbyDevice != null) { - // For existing devices, update their RSSI. - nearbyDevice.updateLastSeen(candidateNearbyDevice.getLastRSSI()); - mNearbyDeviceAdapter.updateListUI(); - } else { - // For new devices, add the device to the adapter. - nearbyDevice = candidateNearbyDevice; - if (nearbyDevice.isBroadcastingUrl()) { - if (!mIsQueuing) { - mIsQueuing = true; - // We wait QUERY_PERIOD ms to see if any other devices are discovered so we can batch. - mQueryHandler.postAtTime(mBatchMetadataRunnable, QUERY_PERIOD); - } - // Add the device to the queue of devices to look for. - mDeviceBatchList.add(nearbyDevice); - mNearbyDeviceAdapter.addDevice(nearbyDevice); - } - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java deleted file mode 100644 index 5e71f5927d..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java +++ /dev/null @@ -1,1012 +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 com.google.samples.apps.iosched.provider; - -import android.app.SearchManager; -import android.content.Context; -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.ContactsContract; -import android.text.TextUtils; -import android.text.format.DateUtils; - -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.samples.apps.iosched.util.ParserUtils; - -import java.util.List; - -/** - * Contract class for interacting with {@link ScheduleProvider}. Unless - * otherwise noted, all time-based fields are milliseconds since epoch and can - * be compared against {@link System#currentTimeMillis()}. - *

- * The backing {@link android.content.ContentProvider} assumes that {@link Uri} - * are generated using stronger {@link String} identifiers, instead of - * {@code int} {@link BaseColumns#_ID} values, which are prone to shuffle during - * sync. - */ -public class ScheduleContract { - /** - * Query parameter to create a distinct query. - */ - public static final String QUERY_PARAMETER_DISTINCT = "distinct"; - public static final String OVERRIDE_ACCOUNTNAME_PARAMETER = "overrideAccount"; - - public interface SyncColumns { - /** Last time this entry was updated or synchronized. */ - String UPDATED = "updated"; - } - - interface BlocksColumns { - /** Unique string identifying this block of time. */ - String BLOCK_ID = "block_id"; - /** Title describing this block of time. */ - String BLOCK_TITLE = "block_title"; - /** Time when this block starts. */ - String BLOCK_START = "block_start"; - /** Time when this block ends. */ - String BLOCK_END = "block_end"; - /** Type describing this block. */ - String BLOCK_TYPE = "block_type"; - /** Extra subtitle for the block. */ - String BLOCK_SUBTITLE = "block_subtitle"; - } - - interface TagsColumns { - /** Unique string identifying this tag. For example, "TOPIC_ANDROID", "TYPE_CODELAB" */ - String TAG_ID = "tag_id"; - /** - * Tag category. For example, the tags that identify what topic a session pertains - * to might belong to the "TOPIC" category; the tags that identify what type a session - * is (codelab, office hours, etc) might belong to the "TYPE" category. - */ - String TAG_CATEGORY = "tag_category"; - /** Tag name. For example, "Android" */ - String TAG_NAME = "tag_name"; - /** Tag's order in its category (for sorting). */ - String TAG_ORDER_IN_CATEGORY = "tag_order_in_category"; - /** Tag's color, in integer format. */ - String TAG_COLOR = "tag_color"; - /** Tag abstract. Short summary describing tag. */ - String TAG_ABSTRACT = "tag_abstract"; - } - - interface RoomsColumns { - /** Unique string identifying this room. */ - String ROOM_ID = "room_id"; - /** Name describing this room. */ - String ROOM_NAME = "room_name"; - /** Building floor this room exists on. */ - String ROOM_FLOOR = "room_floor"; - } - - interface MyScheduleColumns { - String SESSION_ID = SessionsColumns.SESSION_ID; - /** Account name for which the session is starred (in my schedule) */ - String MY_SCHEDULE_ACCOUNT_NAME = "account_name"; - /** Indicate if last operation was "add" (true) or "remove" (false). Since uniqueness is - * given by seesion_id+account_name, this field can be used as a way to find removals and - * sync them with the cloud */ - String MY_SCHEDULE_IN_SCHEDULE = "in_schedule"; - /** Flag to indicate if the corresponding in_my_schedule item needs to be synced */ - String MY_SCHEDULE_DIRTY_FLAG = "dirty"; - } - - interface SessionsColumns { - /** Unique string identifying this session. */ - String SESSION_ID = "session_id"; - /** Difficulty level of the session. */ - String SESSION_LEVEL = "session_level"; - /** Start time of this track. */ - String SESSION_START = "session_start"; - /** End time of this track. */ - String SESSION_END = "session_end"; - /** Title describing this track. */ - String SESSION_TITLE = "session_title"; - /** Body of text explaining this session in detail. */ - String SESSION_ABSTRACT = "session_abstract"; - /** Requirements that attendees should meet. */ - String SESSION_REQUIREMENTS = "session_requirements"; - /** Kewords/tags for this session. */ - String SESSION_KEYWORDS = "session_keywords"; - /** Hashtag for this session. */ - String SESSION_HASHTAG = "session_hashtag"; - /** Full URL to session online. */ - String SESSION_URL = "session_url"; - /** Full URL to YouTube. */ - String SESSION_YOUTUBE_URL = "session_youtube_url"; - /** Full URL to PDF. */ - String SESSION_PDF_URL = "session_pdf_url"; - /** Full URL to official session notes. */ - String SESSION_NOTES_URL = "session_notes_url"; - /** User-specific flag indicating starred status. */ - String SESSION_IN_MY_SCHEDULE = "session_in_my_schedule"; - /** Key for session Calendar event. (Used in ICS or above) */ - String SESSION_CAL_EVENT_ID = "session_cal_event_id"; - /** The YouTube live stream URL. */ - String SESSION_LIVESTREAM_URL = "session_livestream_url"; - /** The Moderator URL. */ - String SESSION_MODERATOR_URL = "session_moderator_url"; - /** The set of tags the session has. This is a comma-separated list of tags.*/ - String SESSION_TAGS = "session_tags"; - /** The names of the speakers on this session, formatted for display. */ - String SESSION_SPEAKER_NAMES = "session_speaker_names"; - /** The order (for sorting) of this session's type. */ - String SESSION_GROUPING_ORDER = "session_grouping_order"; - /** The hashcode of the data used to create this record. */ - String SESSION_IMPORT_HASHCODE = "session_import_hashcode"; - /** The session's main tag. */ - String SESSION_MAIN_TAG = "session_main_tag"; - /** The session's branding color */ - String SESSION_COLOR = "session_color"; - /** The session's captions URL (for livestreamed sessions). */ - String SESSION_CAPTIONS_URL = "session_captions_url"; - /** The session interval when using the interval counter query. */ - String SESSION_INTERVAL_COUNT= "session_interval_count"; - /** The session's photo URL. */ - String SESSION_PHOTO_URL = "session_photo_url"; - /** The session's related content (videos and call to action links). */ - String SESSION_RELATED_CONTENT = "session_related_content"; - } - - interface SpeakersColumns { - /** Unique string identifying this speaker. */ - String SPEAKER_ID = "speaker_id"; - /** Name of this speaker. */ - String SPEAKER_NAME = "speaker_name"; - /** Profile photo of this speaker. */ - String SPEAKER_IMAGE_URL = "speaker_image_url"; - /** Company this speaker works for. */ - String SPEAKER_COMPANY = "speaker_company"; - /** Body of text describing this speaker in detail. */ - String SPEAKER_ABSTRACT = "speaker_abstract"; - /** Full URL to the speaker's profile. */ - String SPEAKER_URL = "speaker_url"; - /** The hashcode of the data used to create this record. */ - String SPEAKER_IMPORT_HASHCODE = "speaker_import_hashcode"; - } - - interface AnnouncementsColumns { - /** Unique string identifying this announcment. */ - String ANNOUNCEMENT_ID = "announcement_id"; - /** Title of the announcement. */ - String ANNOUNCEMENT_TITLE = "announcement_title"; - /** Google+ activity JSON for the announcement. */ - String ANNOUNCEMENT_ACTIVITY_JSON = "announcement_activity_json"; - /** Full URL for the announcement. */ - String ANNOUNCEMENT_URL = "announcement_url"; - /** Date of the announcement. */ - String ANNOUNCEMENT_DATE = "announcement_date"; - } - - interface MapMarkerColumns { - /** Unique string identifying this marker. */ - String MARKER_ID = "map_marker_id"; - /** Type of marker. */ - String MARKER_TYPE = "map_marker_type"; - /** Latitudinal position of marker. */ - String MARKER_LATITUDE = "map_marker_latitude"; - /** Longitudinal position of marker. */ - String MARKER_LONGITUDE = "map_marker_longitude"; - /** Label (title) for this marker. */ - String MARKER_LABEL = "map_marker_label"; - /** Building floor this marker is on. */ - String MARKER_FLOOR = "map_marker_floor"; - } - - interface FeedbackColumns { - String SESSION_ID = "session_id"; - String SESSION_RATING = "feedback_session_rating"; - String ANSWER_RELEVANCE = "feedback_answer_q1"; - String ANSWER_CONTENT = "feedback_answer_q2"; - String ANSWER_SPEAKER = "feedback_answer_q3"; - String COMMENTS = "feedback_comments"; - String SYNCED = "synced"; - } - - interface MapTileColumns { - /** Floor **/ - String TILE_FLOOR = "map_tile_floor"; - /** Filename **/ - String TILE_FILE = "map_tile_file"; - /** Url **/ - String TILE_URL = "map_tile_url"; - } - - interface ExpertsColumns { - /** Unique string identifying this expert. */ - String EXPERT_ID = "expert_id"; - /** Name of this expert. */ - String EXPERT_NAME = "expert_name"; - /** Profile photo of this expert. */ - String EXPERT_IMAGE_URL = "expert_image_url"; - /** Title of this expert. */ - String EXPERT_TITLE = "expert_title"; - /** Body of text describing this expert in detail. */ - String EXPERT_ABSTRACT = "expert_abstract"; - /** Full URL to the expert's profile. */ - String EXPERT_URL = "expert_url"; - /** Country code of this expert. */ - String EXPERT_COUNTRY = "expert_country"; - /** City of this expert. */ - String EXPERT_CITY = "expert_city"; - /** Whether the expert is attending the I/O this year. */ - String EXPERT_ATTENDING = "expert_attending"; - /** The hashcode of the data used to create this record. */ - String EXPERT_IMPORT_HASHCODE = "expert_import_hashcode"; - } - - interface PartnersColumns { - /** Unique string identifying this partner. */ - String PARTNER_ID = "partner_id"; - /** Name of this partner. */ - String PARTNER_NAME = "partner_name"; - /** Description of this partner. */ - String PARTNER_DESC = "partner_desc"; - /** Website URL for this partner. */ - String PARTNER_WEBSITE_URL = "partner_website_url"; - /** Logo URL for this partner. */ - String PARTNER_LOGO_URL = "partner_logo_url"; - } - - interface HashtagColumns { - /** Hashtags */ - String HASHTAG_NAME = "hashtag_name"; - /** Description about this hashtag */ - String HASHTAG_DESCRIPTION = "hashtag_description"; - /** Text color for this hashtag */ - String HASHTAG_COLOR = "hashtag_color"; - /** Ordering of this hashtag */ - String HASHTAG_ORDER = "hashtag_order"; - } - - interface PeopleIveMetColumns { - /** Google+ ID of the person */ - String PERSON_ID = "person_id"; - /** Time when the badge of this person was scanned */ - String PERSON_TIMESTAMP = "person_timestamp"; - /** Name of the person */ - String PERSON_NAME = "person_name"; - /** URL of profile icon of this person */ - String PERSON_IMAGE_URL = "person_image_url"; - /** Note about this person */ - String PERSON_NOTE = "person_note"; - } - - interface VideoColumns { - /** Unique string identifying this video. */ - String VIDEO_ID = "video_id"; - /** Year of the video (e.g. 2014, 2013, ...). */ - String VIDEO_YEAR = "video_year"; - /** Title of the video. */ - String VIDEO_TITLE = "video_title"; - /** Description of the video. */ - String VIDEO_DESC = "video_desc"; - /** Youtube video ID (just the alphanumeric string, not the whole URL). */ - String VIDEO_VID = "video_vid"; - /** Topic (e.g. "Android"). */ - String VIDEO_TOPIC = "video_topic"; - /** Speaker(s) (e.g. "Lauren Ipsum"). */ - String VIDEO_SPEAKERS = "video_speakers"; - /** Thumbnail url. */ - String VIDEO_THUMBNAIL_URL = "video_thumbnail_url"; - /** Import hashcode */ - String VIDEO_IMPORT_HASHCODE = "video_import_hashcode"; - } - - public static final String CONTENT_AUTHORITY = "com.google.samples.apps.iosched"; - - public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY); - - private static final String PATH_BLOCKS = "blocks"; - private static final String PATH_AFTER = "after"; - private static final String PATH_TAGS = "tags"; - private static final String PATH_ROOM = "room"; - private static final String PATH_UNSCHEDULED = "unscheduled"; - private static final String PATH_ROOMS = "rooms"; - private static final String PATH_SESSIONS = "sessions"; - private static final String PATH_FEEDBACK = "feedback"; - private static final String PATH_MY_SCHEDULE = "my_schedule"; - private static final String PATH_SESSIONS_COUNTER = "counter"; - private static final String PATH_SPEAKERS = "speakers"; - private static final String PATH_ANNOUNCEMENTS = "announcements"; - private static final String PATH_MAP_MARKERS = "mapmarkers"; - private static final String PATH_MAP_FLOOR = "floor"; - private static final String PATH_MAP_TILES= "maptiles"; - private static final String PATH_HASHTAGS = "hashtags"; - private static final String PATH_VIDEOS = "videos"; - private static final String PATH_SEARCH = "search"; - private static final String PATH_SEARCH_SUGGEST = "search_suggest_query"; - private static final String PATH_SEARCH_INDEX = "search_index"; - private static final String PATH_EXPERTS = "experts"; - private static final String PATH_PARTNERS = "partners"; - private static final String PATH_PEOPLE_IVE_MET = "people_ive_met"; - - public static final String[] TOP_LEVEL_PATHS = { - PATH_BLOCKS, - PATH_TAGS, - PATH_ROOMS, - PATH_SESSIONS, - PATH_FEEDBACK, - PATH_MY_SCHEDULE, - PATH_SPEAKERS, - PATH_ANNOUNCEMENTS, - PATH_MAP_MARKERS, - PATH_MAP_FLOOR, - PATH_MAP_MARKERS, - PATH_MAP_TILES, - PATH_HASHTAGS, - PATH_VIDEOS, - PATH_EXPERTS, - PATH_PARTNERS, - PATH_PEOPLE_IVE_MET - }; - - public static final String[] USER_DATA_RELATED_PATHS = { - PATH_SESSIONS, - PATH_MY_SCHEDULE - }; - - /** - * Blocks are generic timeslots. - */ - public static class Blocks implements BlocksColumns, BaseColumns { - public static final String BLOCK_TYPE_FREE = "free"; - public static final String BLOCK_TYPE_BREAK = "break"; - public static final String BLOCK_TYPE_KEYNOTE = "keynote"; - - public static final boolean isValidBlockType(String type) { - return BLOCK_TYPE_FREE.equals(type) || BLOCK_TYPE_BREAK.equals(type) - || BLOCK_TYPE_KEYNOTE.equals(type); - } - - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_BLOCKS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.block"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.block"; - - /** "ORDER BY" clauses. */ - public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START + " ASC, " - + BlocksColumns.BLOCK_END + " ASC"; - - /** Build {@link Uri} for requested {@link #BLOCK_ID}. */ - public static Uri buildBlockUri(String blockId) { - return CONTENT_URI.buildUpon().appendPath(blockId).build(); - } - - /** Read {@link #BLOCK_ID} from {@link Blocks} {@link Uri}. */ - public static String getBlockId(Uri uri) { - return uri.getPathSegments().get(1); - } - - /** - * Generate a {@link #BLOCK_ID} that will always match the requested - * {@link Blocks} details. - * @param startTime the block start time, in milliseconds since Epoch UTC - * @param endTime the block end time, in milliseconds since Epoch UTF - */ - public static String generateBlockId(long startTime, long endTime) { - startTime /= DateUtils.SECOND_IN_MILLIS; - endTime /= DateUtils.SECOND_IN_MILLIS; - return ParserUtils.sanitizeId(startTime + "-" + endTime); - } - } - - /** - * Tags represent Session classifications. A session can have many tags. Tags can indicate, - * for example, what product a session pertains to (Android, Chrome, ...), what type - * of session it is (session, codelab, office hours, ...) and what overall event theme - * it falls under (Design, Develop, Distribute), amongst others. - */ - public static class Tags implements TagsColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_TAGS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.tag"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.tag"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = TagsColumns.TAG_ORDER_IN_CATEGORY; - - /** - * Build {@link Uri} that references all tags. - */ - public static Uri buildTagsUri() { - return CONTENT_URI; - } - - /** Build a {@link Uri} that references a given tag. */ - public static Uri buildTagUri(String tagId) { - return CONTENT_URI.buildUpon().appendPath(tagId).build(); - } - - /** Read {@link #TAG_ID} from {@link Tags} {@link Uri}. */ - public static String getTagId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * MySchedule represent the sessions that the user has starred/added to the "my schedule". - * Each row of MySchedule represents one session in one account's my schedule. - */ - public static class MySchedule implements MyScheduleColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_MY_SCHEDULE).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.myschedule"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.myschedule"; - - /** - * Build {@link Uri} that references all My Schedule for the current user. - */ - public static Uri buildMyScheduleUri(Context context) { - return buildMyScheduleUri(context, null); - } - public static Uri buildMyScheduleUri(Context context, String accountName) { - if (accountName == null) { - accountName = AccountUtils.getActiveAccountName(context); - } - return addOverrideAccountName(CONTENT_URI, accountName); - } - - } - - /** - * Rooms are physical locations at the conference venue. - */ - public static class Rooms implements RoomsColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_ROOMS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.room"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.room"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = RoomsColumns.ROOM_FLOOR + " ASC, " - + RoomsColumns.ROOM_NAME + " COLLATE NOCASE ASC"; - - /** Build {@link Uri} for requested {@link #ROOM_ID}. */ - public static Uri buildRoomUri(String roomId) { - return CONTENT_URI.buildUpon().appendPath(roomId).build(); - } - - /** - * Build {@link Uri} that references any {@link Sessions} associated - * with the requested {@link #ROOM_ID}. - */ - public static Uri buildSessionsDirUri(String roomId) { - return CONTENT_URI.buildUpon().appendPath(roomId).appendPath(PATH_SESSIONS).build(); - } - - /** Read {@link #ROOM_ID} from {@link Rooms} {@link Uri}. */ - public static String getRoomId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - public static class Feedback implements BaseColumns, FeedbackColumns, SyncColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_FEEDBACK).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.session_feedback"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.session_feedback"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = BaseColumns._ID + " ASC, "; - - /** Build {@link Uri} to feedback for given session. */ - public static Uri buildFeedbackUri(String sessionId) { - return CONTENT_URI.buildUpon().appendPath(sessionId).build(); - } - - /** Read {@link #SESSION_ID} from {@link Feedback} {@link Uri}. */ - public static String getSessionId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * Each session has zero or more {@link Tags}, a {@link Rooms}, - * zero or more {@link Speakers}. - */ - public static class Sessions implements SessionsColumns, RoomsColumns, - SyncColumns, BaseColumns { - public static final String QUERY_PARAMETER_TAG_FILTER = "filter"; - - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_SESSIONS).build(); - public static final Uri CONTENT_MY_SCHEDULE_URI = - CONTENT_URI.buildUpon().appendPath(PATH_MY_SCHEDULE).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.session"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.session"; - - public static final String ROOM_ID = "room_id"; - - public static final String SEARCH_SNIPPET = "search_snippet"; - - public static final String HAS_GIVEN_FEEDBACK = "has_given_feedback"; - - // ORDER BY clauses - public static final String SORT_BY_TYPE_THEN_TIME = SESSION_GROUPING_ORDER + " ASC," - + SESSION_START + " ASC," + SESSION_TITLE + " COLLATE NOCASE ASC"; - public static final String SORT_BY_TIME = SESSION_START + " ASC," - + SESSION_TITLE + " COLLATE NOCASE ASC"; - - public static final String LIVESTREAM_SELECTION = - SESSION_LIVESTREAM_URL + " is not null AND " + SESSION_LIVESTREAM_URL + "!=''"; - - // Used to fetch sessions starting within a specific time interval - public static final String STARTING_AT_TIME_INTERVAL_SELECTION = - SESSION_START + " >= ? and " + SESSION_START + " <= ?"; - - // Used to fetch sessions for a particular time - public static final String AT_TIME_SELECTION = - SESSION_START + " <= ? and " + SESSION_END + " >= ?"; - - // Builds selectionArgs for {@link STARTING_AT_TIME_INTERVAL_SELECTION} - public static String[] buildAtTimeIntervalArgs(long intervalStart, long intervalEnd) { - return new String[] { String.valueOf(intervalStart), String.valueOf(intervalEnd) }; - } - - // Builds selectionArgs for {@link AT_TIME_SELECTION} - public static String[] buildAtTimeSelectionArgs(long time) { - final String timeString = String.valueOf(time); - return new String[] { timeString, timeString }; - } - - // Used to fetch upcoming sessions - public static final String UPCOMING_LIVE_SELECTION = SESSION_START + " > ?"; - - // Builds selectionArgs for {@link UPCOMING_LIVE_SELECTION} - public static String[] buildUpcomingSelectionArgs(long minTime) { - return new String[] { String.valueOf(minTime) }; - } - - /** Build {@link Uri} for requested {@link #SESSION_ID}. */ - public static Uri buildSessionUri(String sessionId) { - return CONTENT_URI.buildUpon().appendPath(sessionId).build(); - } - - /** - * Build {@link Uri} that references any {@link Speakers} associated - * with the requested {@link #SESSION_ID}. - */ - public static Uri buildSpeakersDirUri(String sessionId) { - return CONTENT_URI.buildUpon().appendPath(sessionId).appendPath(PATH_SPEAKERS).build(); - } - - /** - * Build {@link Uri} that references any {@link Tags} associated with - * the requested {@link #SESSION_ID}. - */ - public static Uri buildTagsDirUri(String sessionId) { - return CONTENT_URI.buildUpon().appendPath(sessionId).appendPath(PATH_TAGS).build(); - } - - /** - * Build {@link Uri} that references sessions that match the query. The query can be - * multiple words separated with spaces. - * - * @param query The query. Can be multiple words separated by spaces. - * @return {@link Uri} to the sessions - */ - public static Uri buildSearchUri(String query) { - if (null == query) { - query = ""; - } - // convert "lorem ipsum dolor sit" to "lorem* ipsum* dolor* sit*" - query = query.replaceAll(" +", " *") + "*"; - return CONTENT_URI.buildUpon() - .appendPath(PATH_SEARCH).appendPath(query).build(); - } - - public static boolean isSearchUri(Uri uri) { - List pathSegments = uri.getPathSegments(); - return pathSegments.size() >= 2 && PATH_SEARCH.equals(pathSegments.get(1)); - } - - /** Build {@link Uri} that references sessions in a room that have begun after the requested time **/ - public static Uri buildSessionsInRoomAfterUri(String room, long time) { - return CONTENT_URI.buildUpon().appendPath(PATH_ROOM).appendPath(room).appendPath(PATH_AFTER) - .appendPath(String.valueOf(time)).build(); - } - - /** Build {@link Uri} that references sessions not in user's schedule that happen in the specified interval **/ - public static Uri buildUnscheduledSessionsInInterval(long start, long end) { - String interval = start+"-"+end; - return CONTENT_URI.buildUpon().appendPath(PATH_UNSCHEDULED).appendPath(interval).build(); - } - - public static boolean isUnscheduledSessionsInInterval(Uri uri) { - return uri != null && uri.toString().startsWith( - CONTENT_URI.buildUpon().appendPath(PATH_UNSCHEDULED).toString()); - } - - public static long[] getInterval(Uri uri) { - if (uri == null) { - return null; - } - List segments = uri.getPathSegments(); - if (segments.size() == 3 && segments.get(2).indexOf('-') > 0 ) { - String[] interval = segments.get(2).split("-"); - return new long[]{Long.parseLong(interval[0]), Long.parseLong(interval[1])}; - } - return null; - } - - public static String getRoom(Uri uri){ - return uri.getPathSegments().get(2); - } - - public static String getAfter(Uri uri){ - return uri.getPathSegments().get(4); - } - - - /** Read {@link #SESSION_ID} from {@link Sessions} {@link Uri}. */ - public static String getSessionId(Uri uri) { - return uri.getPathSegments().get(1); - } - - public static String getSearchQuery(Uri uri) { - List segments = uri.getPathSegments(); - if (2 < segments.size()) { - return segments.get(2); - } - return null; - } - - public static boolean hasFilterParam(Uri uri) { - return uri != null && uri.getQueryParameter(QUERY_PARAMETER_TAG_FILTER) != null; - } - - /** Build {@link Uri} that references all sessions that have ALL of the indicated tags. */ - public static Uri buildTagFilterUri(String[] requiredTags) { - StringBuilder sb = new StringBuilder(); - for (String tag : requiredTags) { - if (TextUtils.isEmpty(tag)) continue; - if (sb.length() > 0) { - sb.append(","); - } - sb.append(tag.trim()); - } - if (sb.length() == 0) { - // equivalent to "all sessions" - return CONTENT_URI; - } else { - // filter by the given set of tags - return CONTENT_URI.buildUpon().appendQueryParameter(QUERY_PARAMETER_TAG_FILTER, - sb.toString()).build(); - } - } - - /** Build {@link Uri} that counts sessions by start/end intervals. */ - public static Uri buildCounterByIntervalUri() { - return CONTENT_URI.buildUpon().appendPath(PATH_SESSIONS_COUNTER).build(); - } - } - - /** - * Speakers are individual people that lead {@link Sessions}. - */ - public static class Speakers implements SpeakersColumns, SyncColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_SPEAKERS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.speaker"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.speaker"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = SpeakersColumns.SPEAKER_NAME - + " COLLATE NOCASE ASC"; - - /** Build {@link Uri} for requested {@link #SPEAKER_ID}. */ - public static Uri buildSpeakerUri(String speakerId) { - return CONTENT_URI.buildUpon().appendPath(speakerId).build(); - } - - /** - * Build {@link Uri} that references any {@link Sessions} associated - * with the requested {@link #SPEAKER_ID}. - */ - public static Uri buildSessionsDirUri(String speakerId) { - return CONTENT_URI.buildUpon().appendPath(speakerId).appendPath(PATH_SESSIONS).build(); - } - - /** Read {@link #SPEAKER_ID} from {@link Speakers} {@link Uri}. */ - public static String getSpeakerId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * Announcements of breaking news - */ - public static class Announcements implements AnnouncementsColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_ANNOUNCEMENTS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.announcement"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.announcement"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = AnnouncementsColumns.ANNOUNCEMENT_DATE - + " COLLATE NOCASE DESC"; - - /** Build {@link Uri} for requested {@link #ANNOUNCEMENT_ID}. */ - public static Uri buildAnnouncementUri(String announcementId) { - return CONTENT_URI.buildUpon().appendPath(announcementId).build(); - } - - /** - * Read {@link #ANNOUNCEMENT_ID} from {@link Announcements} {@link Uri}. - */ - public static String getAnnouncementId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * TileProvider entries are used to create an overlay provider for the map. - */ - public static class MapTiles implements MapTileColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon() - .appendPath(PATH_MAP_TILES).build(); - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.maptiles"; - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.maptiles"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = MapTileColumns.TILE_FLOOR + " ASC"; - - - /** Build {@link Uri} for all overlay zoom entries */ - public static Uri buildUri() { - return CONTENT_URI; - } - - /** Build {@link Uri} for requested floor. */ - public static Uri buildFloorUri(String floor) { - return CONTENT_URI.buildUpon() - .appendPath(String.valueOf(floor)).build(); - } - - /** Read floor from {@link MapMarkers} {@link Uri}. */ - public static String getFloorId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * Markers refer to marked positions on the map. - */ - public static class MapMarkers implements MapMarkerColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon() - .appendPath(PATH_MAP_MARKERS).build(); - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.mapmarker"; - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.mapmarker"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = MapMarkerColumns.MARKER_FLOOR - + " ASC, " + MapMarkerColumns.MARKER_ID + " ASC"; - - /** Build {@link Uri} for requested {@link #MARKER_ID}. */ - public static Uri buildMarkerUri(String markerId) { - return CONTENT_URI.buildUpon().appendPath(markerId).build(); - } - - /** Build {@link Uri} for all markers */ - public static Uri buildMarkerUri() { - return CONTENT_URI; - } - - /** Build {@link Uri} for requested {@link #MARKER_ID}. */ - public static Uri buildFloorUri(int floor) { - return CONTENT_URI.buildUpon().appendPath(PATH_MAP_FLOOR) - .appendPath("" + floor).build(); - } - - /** Read {@link #MARKER_ID} from {@link MapMarkers} {@link Uri}. */ - public static String getMarkerId(Uri uri) { - return uri.getPathSegments().get(1); - } - - /** Read FLOOR from {@link MapMarkers} {@link Uri}. */ - public static String getMarkerFloor(Uri uri) { - return uri.getPathSegments().get(2); - } - - } - - /** - * Hashtags are used for Google+ search. This model is independent from other models. - */ - public static class Hashtags implements HashtagColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon() - .appendPath(PATH_HASHTAGS).build(); - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.hashtags"; - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.hashtags"; - - /** Build {@link Uri} for requested hashtag. */ - public static Uri buildHashtagUri(String hashtag) { - return CONTENT_URI.buildUpon().appendPath(hashtag).build(); - } - - /** Read hashtag from {@link Hashtags} {@link Uri}. */ - public static String getHashtagName(Uri uri) { - return uri.getPathSegments().get(1); - } - - } - - /** - * Videos are displayed in the Video Library. They are links to Youtube plus metadata. - */ - public static class Videos implements VideoColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon() - .appendPath(PATH_VIDEOS).build(); - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.videos"; - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.videos"; - - public static final String DEFAULT_SORT = VideoColumns.VIDEO_YEAR + " DESC, " - + VideoColumns.VIDEO_TOPIC + " ASC, " + VideoColumns.VIDEO_TITLE + " ASC"; - - /** Build {@link Uri} for given video */ - public static Uri buildVideoUri(String videoId) { - return CONTENT_URI.buildUpon().appendPath(videoId).build(); - } - - /** Return video ID given URI */ - public static String getVideoId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - public static class SearchSuggest { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_SEARCH_SUGGEST).build(); - - public static final String DEFAULT_SORT = SearchManager.SUGGEST_COLUMN_TEXT_1 - + " COLLATE NOCASE ASC"; - } - - public static class SearchIndex { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_SEARCH_INDEX).build(); - } - - /** - * Experts are individual people. Independent from the other data models. - */ - public static class Experts implements ExpertsColumns, SyncColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_EXPERTS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.expert"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.expert"; - - /** Default "ORDER BY" clause. */ - public static final String DEFAULT_SORT = ExpertsColumns.EXPERT_NAME - + " COLLATE NOCASE ASC"; - - /** Build {@link Uri} for requested {@link #EXPERT_ID}. */ - public static Uri buildExpertUri(String expertId) { - return CONTENT_URI.buildUpon().appendPath(expertId).build(); - } - - public static String getExpertId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - /** - * Partners are companies presenting at IO. This model is independent from other data models. - */ - public static class Partners implements PartnersColumns, SyncColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_PARTNERS).build(); - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.partner"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.partner"; - - /** Build {@link Uri} for requested {@link #PARTNER_ID}. */ - public static Uri buildPartnerUri(String partnerId) { - return CONTENT_URI.buildUpon().appendPath(partnerId).build(); - } - - public static String getPartnerId(Uri uri) { return uri.getPathSegments().get(1); } - } - - /** - * Each record of PeopleIveMet is collected by scanning their badges. This model is independent - * from other models. - */ - public static class PeopleIveMet implements PeopleIveMetColumns, BaseColumns { - public static final Uri CONTENT_URI = - BASE_CONTENT_URI.buildUpon().appendPath(PATH_PEOPLE_IVE_MET).build(); - - public static final String DEFAULT_SORT = PeopleIveMetColumns.PERSON_TIMESTAMP + " DESC"; - - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.iosched2014.people_ive_met"; - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.iosched2014.people_ive_met"; - - public static Uri buildPersonUri(String personId) { - return CONTENT_URI.buildUpon().appendPath(personId).build(); - } - - public static String getPersonId(Uri uri) { - return uri.getPathSegments().get(1); - } - } - - public static Uri addCallerIsSyncAdapterParameter(Uri uri) { - return uri.buildUpon().appendQueryParameter( - ContactsContract.CALLER_IS_SYNCADAPTER, "true").build(); - } - - public static boolean hasCallerIsSyncAdapterParameter(Uri uri) { - return TextUtils.equals("true", - uri.getQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER)); - } - - /** - * Adds an account override parameter to the URI. - * The override parameter instructs the Content Provider to ignore the currently logged in - * account and use the provided account when fetching account-specific data - * (such as sessions in My Schedule). - * - */ - public static Uri addOverrideAccountName(Uri uri, String accountName) { - return uri.buildUpon().appendQueryParameter( - OVERRIDE_ACCOUNTNAME_PARAMETER, accountName).build(); - } - - public static String getOverrideAccountName(Uri uri) { - return uri.getQueryParameter(OVERRIDE_ACCOUNTNAME_PARAMETER); - } - - private ScheduleContract() { - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.java deleted file mode 100644 index 79f59ea10c..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.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 com.google.samples.apps.iosched.provider; - -import android.accounts.Account; -import android.app.SearchManager; -import android.content.ContentResolver; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.provider.BaseColumns; - -import com.google.samples.apps.iosched.provider.ScheduleContract.*; -import com.google.samples.apps.iosched.sync.ConferenceDataHandler; -import com.google.samples.apps.iosched.sync.SyncHelper; -import com.google.samples.apps.iosched.util.AccountUtils; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Helper for managing {@link SQLiteDatabase} that stores data for - * {@link ScheduleProvider}. - */ -public class ScheduleDatabase extends SQLiteOpenHelper { - private static final String TAG = makeLogTag(ScheduleDatabase.class); - - private static final String DATABASE_NAME = "schedule.db"; - - // NOTE: carefully update onUpgrade() when bumping database versions to make - // sure user data is saved. - - private static final int VER_2014_RELEASE_A = 122; // app version 2.0.0, 2.0.1 - private static final int VER_2014_RELEASE_C = 207; // app version 2.1.x - private static final int CUR_DATABASE_VERSION = VER_2014_RELEASE_C; - - private final Context mContext; - - interface Tables { - String BLOCKS = "blocks"; - String TAGS = "tags"; - String ROOMS = "rooms"; - String SESSIONS = "sessions"; - String MY_SCHEDULE = "myschedule"; - String SPEAKERS = "speakers"; - String SESSIONS_TAGS = "sessions_tags"; - String SESSIONS_SPEAKERS = "sessions_speakers"; - String ANNOUNCEMENTS = "announcements"; - String MAPMARKERS = "mapmarkers"; - String MAPTILES = "mapoverlays"; - String HASHTAGS = "hashtags"; - String FEEDBACK = "feedback"; - String EXPERTS = "experts"; - String PEOPLE_IVE_MET = "people_ive_met"; - String VIDEOS = "videos"; - String PARTNERS = "partners"; - - String SESSIONS_SEARCH = "sessions_search"; - - String SEARCH_SUGGEST = "search_suggest"; - - String SESSIONS_JOIN_MYSCHEDULE = "sessions " - + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id " - + "AND myschedule.account_name=? "; - - String SESSIONS_JOIN_ROOMS_TAGS = "sessions " - + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id " - + "AND myschedule.account_name=? " - + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id " - + "LEFT OUTER JOIN sessions_tags ON sessions.session_id=sessions_tags.session_id"; - - String SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE = "sessions " - + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id " - + "AND myschedule.account_name=? " - + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id " - + "LEFT OUTER JOIN sessions_tags ON sessions.session_id=sessions_tags.session_id " - + "LEFT OUTER JOIN feedback ON sessions.session_id=feedback.session_id"; - - String SESSIONS_JOIN_ROOMS = "sessions " - + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id " - + "AND myschedule.account_name=? " - + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id"; - - String SESSIONS_SPEAKERS_JOIN_SPEAKERS = "sessions_speakers " - + "LEFT OUTER JOIN speakers ON sessions_speakers.speaker_id=speakers.speaker_id"; - - String SESSIONS_TAGS_JOIN_TAGS = "sessions_tags " - + "LEFT OUTER JOIN tags ON sessions_tags.tag_id=tags.tag_id"; - - String SESSIONS_SPEAKERS_JOIN_SESSIONS_ROOMS = "sessions_speakers " - + "LEFT OUTER JOIN sessions ON sessions_speakers.session_id=sessions.session_id " - + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id"; - - String SESSIONS_SEARCH_JOIN_SESSIONS_ROOMS = "sessions_search " - + "LEFT OUTER JOIN sessions ON sessions_search.session_id=sessions.session_id " - + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id " - + "AND myschedule.account_name=? " - + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id"; - - // When tables get deprecated, add them to this list (so they get correctly deleted - // on database upgrades) - interface DeprecatedTables { - String TRACKS = "tracks"; - String SESSIONS_TRACKS = "sessions_tracks"; - String SANDBOX = "sandbox"; - }; - - } - - private interface Triggers { - // Deletes from dependent tables when corresponding sessions are deleted. - String SESSIONS_TAGS_DELETE = "sessions_tags_delete"; - String SESSIONS_SPEAKERS_DELETE = "sessions_speakers_delete"; - String SESSIONS_MY_SCHEDULE_DELETE = "sessions_myschedule_delete"; - String SESSIONS_FEEDBACK_DELETE = "sessions_feedback_delete"; - - // When triggers get deprecated, add them to this list (so they get correctly deleted - // on database upgrades) - interface DeprecatedTriggers { - String SESSIONS_TRACKS_DELETE = "sessions_tracks_delete"; - }; - } - - public interface SessionsSpeakers { - String SESSION_ID = "session_id"; - String SPEAKER_ID = "speaker_id"; - } - - public interface SessionsTags { - String SESSION_ID = "session_id"; - String TAG_ID = "tag_id"; - } - - interface SessionsSearchColumns { - String SESSION_ID = "session_id"; - String BODY = "body"; - } - - /** Fully-qualified field names. */ - private interface Qualified { - String SESSIONS_SEARCH = Tables.SESSIONS_SEARCH + "(" + SessionsSearchColumns.SESSION_ID - + "," + SessionsSearchColumns.BODY + ")"; - - String SESSIONS_TAGS_SESSION_ID = Tables.SESSIONS_TAGS + "." - + SessionsTags.SESSION_ID; - - String SESSIONS_SPEAKERS_SESSION_ID = Tables.SESSIONS_SPEAKERS+ "." - + SessionsSpeakers.SESSION_ID; - - String SESSIONS_SPEAKERS_SPEAKER_ID = Tables.SESSIONS_SPEAKERS+ "." - + SessionsSpeakers.SPEAKER_ID; - - String SPEAKERS_SPEAKER_ID = Tables.SPEAKERS + "." + Speakers.SPEAKER_ID; - - String FEEDBACK_SESSION_ID = Tables.FEEDBACK + "." + FeedbackColumns.SESSION_ID; - } - - /** {@code REFERENCES} clauses. */ - private interface References { - String BLOCK_ID = "REFERENCES " + Tables.BLOCKS + "(" + Blocks.BLOCK_ID + ")"; - String TAG_ID = "REFERENCES " + Tables.TAGS + "(" + Tags.TAG_ID + ")"; - String ROOM_ID = "REFERENCES " + Tables.ROOMS + "(" + Rooms.ROOM_ID + ")"; - String SESSION_ID = "REFERENCES " + Tables.SESSIONS + "(" + Sessions.SESSION_ID + ")"; - String SPEAKER_ID = "REFERENCES " + Tables.SPEAKERS + "(" + Speakers.SPEAKER_ID + ")"; - } - - public ScheduleDatabase(Context context) { - super(context, DATABASE_NAME, null, CUR_DATABASE_VERSION); - mContext = context; - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + Tables.BLOCKS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + BlocksColumns.BLOCK_ID + " TEXT NOT NULL," - + BlocksColumns.BLOCK_TITLE + " TEXT NOT NULL," - + BlocksColumns.BLOCK_START + " INTEGER NOT NULL," - + BlocksColumns.BLOCK_END + " INTEGER NOT NULL," - + BlocksColumns.BLOCK_TYPE + " TEXT," - + BlocksColumns.BLOCK_SUBTITLE + " TEXT," - + "UNIQUE (" + BlocksColumns.BLOCK_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.TAGS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + TagsColumns.TAG_ID + " TEXT NOT NULL," - + TagsColumns.TAG_CATEGORY + " TEXT NOT NULL," - + TagsColumns.TAG_NAME + " TEXT NOT NULL," - + TagsColumns.TAG_ORDER_IN_CATEGORY + " INTEGER," - + TagsColumns.TAG_COLOR + " TEXT NOT NULL," - + TagsColumns.TAG_ABSTRACT + " TEXT NOT NULL," - + "UNIQUE (" + TagsColumns.TAG_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.ROOMS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + RoomsColumns.ROOM_ID + " TEXT NOT NULL," - + RoomsColumns.ROOM_NAME + " TEXT," - + RoomsColumns.ROOM_FLOOR + " TEXT," - + "UNIQUE (" + RoomsColumns.ROOM_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.SESSIONS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SyncColumns.UPDATED + " INTEGER NOT NULL," - + SessionsColumns.SESSION_ID + " TEXT NOT NULL," - + Sessions.ROOM_ID + " TEXT " + References.ROOM_ID + "," - + SessionsColumns.SESSION_START + " INTEGER NOT NULL," - + SessionsColumns.SESSION_END + " INTEGER NOT NULL," - + SessionsColumns.SESSION_LEVEL + " TEXT," - + SessionsColumns.SESSION_TITLE + " TEXT," - + SessionsColumns.SESSION_ABSTRACT + " TEXT," - + SessionsColumns.SESSION_REQUIREMENTS + " TEXT," - + SessionsColumns.SESSION_KEYWORDS + " TEXT," - + SessionsColumns.SESSION_HASHTAG + " TEXT," - + SessionsColumns.SESSION_URL + " TEXT," - + SessionsColumns.SESSION_YOUTUBE_URL + " TEXT," - + SessionsColumns.SESSION_MODERATOR_URL + " TEXT," - + SessionsColumns.SESSION_PDF_URL + " TEXT," - + SessionsColumns.SESSION_NOTES_URL + " TEXT," - + SessionsColumns.SESSION_CAL_EVENT_ID + " INTEGER," - + SessionsColumns.SESSION_LIVESTREAM_URL + " TEXT," - + SessionsColumns.SESSION_TAGS + " TEXT," - + SessionsColumns.SESSION_GROUPING_ORDER + " INTEGER," - + SessionsColumns.SESSION_SPEAKER_NAMES + " TEXT," - + SessionsColumns.SESSION_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT ''," - + SessionsColumns.SESSION_MAIN_TAG + " TEXT," - + SessionsColumns.SESSION_COLOR + " INTEGER," - + SessionsColumns.SESSION_CAPTIONS_URL + " TEXT," - + SessionsColumns.SESSION_PHOTO_URL + " TEXT," - + SessionsColumns.SESSION_RELATED_CONTENT + " TEXT," - + "UNIQUE (" + SessionsColumns.SESSION_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.SPEAKERS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SyncColumns.UPDATED + " INTEGER NOT NULL," - + SpeakersColumns.SPEAKER_ID + " TEXT NOT NULL," - + SpeakersColumns.SPEAKER_NAME + " TEXT," - + SpeakersColumns.SPEAKER_IMAGE_URL + " TEXT," - + SpeakersColumns.SPEAKER_COMPANY + " TEXT," - + SpeakersColumns.SPEAKER_ABSTRACT + " TEXT," - + SpeakersColumns.SPEAKER_URL + " TEXT," - + SpeakersColumns.SPEAKER_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT ''," - + "UNIQUE (" + SpeakersColumns.SPEAKER_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.MY_SCHEDULE + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + MySchedule.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + "," - + MySchedule.MY_SCHEDULE_ACCOUNT_NAME + " TEXT NOT NULL ," - + MySchedule.MY_SCHEDULE_DIRTY_FLAG + " INTEGER NOT NULL DEFAULT 1," - + MySchedule.MY_SCHEDULE_IN_SCHEDULE + " INTEGER NOT NULL DEFAULT 1," - + "UNIQUE (" + MySchedule.SESSION_ID + "," - + MySchedule.MY_SCHEDULE_ACCOUNT_NAME + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.SESSIONS_SPEAKERS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SessionsSpeakers.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + "," - + SessionsSpeakers.SPEAKER_ID + " TEXT NOT NULL " + References.SPEAKER_ID + "," - + "UNIQUE (" + SessionsSpeakers.SESSION_ID + "," - + SessionsSpeakers.SPEAKER_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.SESSIONS_TAGS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SessionsTags.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + "," - + SessionsTags.TAG_ID + " TEXT NOT NULL " + References.TAG_ID + "," - + "UNIQUE (" + SessionsTags.SESSION_ID + "," - + SessionsTags.TAG_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.ANNOUNCEMENTS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SyncColumns.UPDATED + " INTEGER NOT NULL," - + AnnouncementsColumns.ANNOUNCEMENT_ID + " TEXT," - + AnnouncementsColumns.ANNOUNCEMENT_TITLE + " TEXT NOT NULL," - + AnnouncementsColumns.ANNOUNCEMENT_ACTIVITY_JSON + " BLOB," - + AnnouncementsColumns.ANNOUNCEMENT_URL + " TEXT," - + AnnouncementsColumns.ANNOUNCEMENT_DATE + " INTEGER NOT NULL)"); - - db.execSQL("CREATE TABLE " + Tables.MAPTILES + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + MapTileColumns.TILE_FLOOR+ " INTEGER NOT NULL," - + MapTileColumns.TILE_FILE+ " TEXT NOT NULL," - + MapTileColumns.TILE_URL+ " TEXT NOT NULL," - + "UNIQUE (" + MapTileColumns.TILE_FLOOR+ ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.FEEDBACK + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SyncColumns.UPDATED + " INTEGER NOT NULL," - + Sessions.SESSION_ID + " TEXT " + References.SESSION_ID + "," - + FeedbackColumns.SESSION_RATING + " INTEGER NOT NULL," - + FeedbackColumns.ANSWER_RELEVANCE + " INTEGER NOT NULL," - + FeedbackColumns.ANSWER_CONTENT + " INTEGER NOT NULL," - + FeedbackColumns.ANSWER_SPEAKER + " INTEGER NOT NULL," - + FeedbackColumns.COMMENTS + " TEXT," - + FeedbackColumns.SYNCED + " INTEGER NOT NULL DEFAULT 0)"); - - db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_FEEDBACK_DELETE + " AFTER DELETE ON " - + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.FEEDBACK + " " - + " WHERE " + Qualified.FEEDBACK_SESSION_ID + "=old." + Sessions.SESSION_ID - + ";" + " END;"); - - db.execSQL("CREATE TABLE " + Tables.MAPMARKERS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + MapMarkerColumns.MARKER_ID+ " TEXT NOT NULL," - + MapMarkerColumns.MARKER_TYPE+ " TEXT NOT NULL," - + MapMarkerColumns.MARKER_LATITUDE+ " DOUBLE NOT NULL," - + MapMarkerColumns.MARKER_LONGITUDE+ " DOUBLE NOT NULL," - + MapMarkerColumns.MARKER_LABEL+ " TEXT," - + MapMarkerColumns.MARKER_FLOOR+ " INTEGER NOT NULL," - + "UNIQUE (" + MapMarkerColumns.MARKER_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.HASHTAGS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + HashtagColumns.HASHTAG_NAME + " TEXT NOT NULL," - + HashtagColumns.HASHTAG_DESCRIPTION + " TEXT NOT NULL," - + HashtagColumns.HASHTAG_COLOR + " INTEGER NOT NULL," - + HashtagColumns.HASHTAG_ORDER + " INTEGER NOT NULL," - + "UNIQUE (" + HashtagColumns.HASHTAG_NAME + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.VIDEOS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + VideoColumns.VIDEO_ID + " TEXT NOT NULL," - + VideoColumns.VIDEO_YEAR + " INTEGER NOT NULL," - + VideoColumns.VIDEO_TITLE + " TEXT," - + VideoColumns.VIDEO_DESC + " TEXT," - + VideoColumns.VIDEO_VID + " TEXT," - + VideoColumns.VIDEO_TOPIC + " TEXT," - + VideoColumns.VIDEO_SPEAKERS + " TEXT," - + VideoColumns.VIDEO_THUMBNAIL_URL + " TEXT," - + VideoColumns.VIDEO_IMPORT_HASHCODE + " TEXT NOT NULL," - + "UNIQUE (" + VideoColumns.VIDEO_ID + ") ON CONFLICT REPLACE)"); - - // Full-text search index. Update using updateSessionSearchIndex method. - // Use the porter tokenizer for simple stemming, so that "frustration" matches "frustrated." - db.execSQL("CREATE VIRTUAL TABLE " + Tables.SESSIONS_SEARCH + " USING fts3(" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SessionsSearchColumns.BODY + " TEXT NOT NULL," - + SessionsSearchColumns.SESSION_ID - + " TEXT NOT NULL " + References.SESSION_ID + "," - + "UNIQUE (" + SessionsSearchColumns.SESSION_ID + ") ON CONFLICT REPLACE," - + "tokenize=porter)"); - - // Search suggestions - db.execSQL("CREATE TABLE " + Tables.SEARCH_SUGGEST + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + SearchManager.SUGGEST_COLUMN_TEXT_1 + " TEXT NOT NULL)"); - - // Session deletion triggers - db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_TAGS_DELETE + " AFTER DELETE ON " - + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.SESSIONS_TAGS + " " - + " WHERE " + Qualified.SESSIONS_TAGS_SESSION_ID + "=old." + Sessions.SESSION_ID - + ";" + " END;"); - - db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_SPEAKERS_DELETE + " AFTER DELETE ON " - + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.SESSIONS_SPEAKERS + " " - + " WHERE " + Qualified.SESSIONS_SPEAKERS_SESSION_ID + "=old." + Sessions.SESSION_ID - + ";" + " END;"); - - db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_MY_SCHEDULE_DELETE + " AFTER DELETE ON " - + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.MY_SCHEDULE + " " - + " WHERE " + Tables.MY_SCHEDULE + "." + MySchedule.SESSION_ID + - "=old." + Sessions.SESSION_ID - + ";" + " END;"); - - upgradeAtoC(db); - } - - private void upgradeAtoC(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + Tables.EXPERTS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + SyncColumns.UPDATED + " INTEGER NOT NULL, " - + ExpertsColumns.EXPERT_ID + " TEXT NOT NULL, " - + ExpertsColumns.EXPERT_NAME + " TEXT, " - + ExpertsColumns.EXPERT_IMAGE_URL + " TEXT, " - + ExpertsColumns.EXPERT_TITLE + " TEXT, " - + ExpertsColumns.EXPERT_ABSTRACT + " TEXT, " - + ExpertsColumns.EXPERT_URL + " TEXT, " - + ExpertsColumns.EXPERT_COUNTRY + " TEXT, " - + ExpertsColumns.EXPERT_CITY + " TEXT, " - + ExpertsColumns.EXPERT_ATTENDING + " BOOLEAN, " - + ExpertsColumns.EXPERT_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT '', " - + "UNIQUE (" + ExpertsColumns.EXPERT_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.PEOPLE_IVE_MET + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ScheduleContract.PeopleIveMetColumns.PERSON_ID + " TEXT NOT NULL, " - + ScheduleContract.PeopleIveMetColumns.PERSON_TIMESTAMP + " INTEGER NOT NULL, " - + ScheduleContract.PeopleIveMetColumns.PERSON_NAME + " TEXT, " - + ScheduleContract.PeopleIveMetColumns.PERSON_IMAGE_URL + " TEXT, " - + ScheduleContract.PeopleIveMetColumns.PERSON_NOTE + " TEXT, " - + "UNIQUE (" + ScheduleContract.PeopleIveMetColumns.PERSON_ID + ") ON CONFLICT REPLACE)"); - - db.execSQL("CREATE TABLE " + Tables.PARTNERS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," - + PartnersColumns.PARTNER_ID + " TEXT NOT NULL," - + PartnersColumns.PARTNER_NAME + " TEXT NOT NULL," - + PartnersColumns.PARTNER_DESC + " TEXT NOT NULL," - + PartnersColumns.PARTNER_WEBSITE_URL + " TEXT NOT NULL," - + PartnersColumns.PARTNER_LOGO_URL + " TEXT NOT NULL," - + "UNIQUE (" + PartnersColumns.PARTNER_ID + ") ON CONFLICT REPLACE)"); - } - - /** - * Updates the session search index. This should be done sparingly, as the queries are rather - * complex. - */ - static void updateSessionSearchIndex(SQLiteDatabase db) { - db.execSQL("DELETE FROM " + Tables.SESSIONS_SEARCH); - - db.execSQL("INSERT INTO " + Qualified.SESSIONS_SEARCH - + " SELECT s." + Sessions.SESSION_ID + ",(" - - // Full text body - + Sessions.SESSION_TITLE + "||'; '||" - + Sessions.SESSION_ABSTRACT + "||'; '||" - + "IFNULL(GROUP_CONCAT(t." + Speakers.SPEAKER_NAME + ",' '),'')||'; '||" - + "'')" - - + " FROM " + Tables.SESSIONS + " s " - + " LEFT OUTER JOIN" - - // Subquery resulting in session_id, speaker_id, speaker_name - + "(SELECT " + Sessions.SESSION_ID + "," + Qualified.SPEAKERS_SPEAKER_ID - + "," + Speakers.SPEAKER_NAME - + " FROM " + Tables.SESSIONS_SPEAKERS - + " INNER JOIN " + Tables.SPEAKERS - + " ON " + Qualified.SESSIONS_SPEAKERS_SPEAKER_ID + "=" - + Qualified.SPEAKERS_SPEAKER_ID - + ") t" - - // Grand finale - + " ON s." + Sessions.SESSION_ID + "=t." + Sessions.SESSION_ID - + " GROUP BY s." + Sessions.SESSION_ID); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - LOGD(TAG, "onUpgrade() from " + oldVersion + " to " + newVersion); - - // Cancel any sync currently in progress - Account account = AccountUtils.getActiveAccount(mContext); - if (account != null) { - LOGI(TAG, "Cancelling any pending syncs for account"); - ContentResolver.cancelSync(account, ScheduleContract.CONTENT_AUTHORITY); - } - - // Current DB version. We update this variable as we perform upgrades to reflect - // the current version we are in. - int version = oldVersion; - - // Indicates whether the data we currently have should be invalidated as a - // result of the db upgrade. Default is true (invalidate); if we detect that this - // is a trivial DB upgrade, we set this to false. - boolean dataInvalidated = true; - - // Check if we can upgrade from release A to release C - if (version == VER_2014_RELEASE_A) { - // release A format can be upgraded to release C format - LOGD(TAG, "Upgrading database from 2014 release A to 2014 release C."); - upgradeAtoC(db); - version = VER_2014_RELEASE_C; - } - - LOGD(TAG, "After upgrade logic, at version " + version); - - // at this point, we ran out of upgrade logic, so if we are still at the wrong - // version, we have no choice but to delete everything and create everything again. - if (version != CUR_DATABASE_VERSION) { - LOGW(TAG, "Upgrade unsuccessful -- destroying old data during upgrade"); - - db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_TAGS_DELETE); - db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_SPEAKERS_DELETE); - db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_FEEDBACK_DELETE); - db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_MY_SCHEDULE_DELETE); - db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.DeprecatedTriggers.SESSIONS_TRACKS_DELETE); - - db.execSQL("DROP TABLE IF EXISTS " + Tables.BLOCKS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.ROOMS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.TAGS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SPEAKERS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.MY_SCHEDULE); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_SPEAKERS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_TAGS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.ANNOUNCEMENTS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.FEEDBACK); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_SEARCH); - db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_SUGGEST); - db.execSQL("DROP TABLE IF EXISTS " + Tables.MAPMARKERS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.MAPTILES); - db.execSQL("DROP TABLE IF EXISTS " + Tables.EXPERTS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.HASHTAGS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.PEOPLE_IVE_MET); - db.execSQL("DROP TABLE IF EXISTS " + Tables.VIDEOS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.PARTNERS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.TRACKS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.SESSIONS_TRACKS); - db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.SANDBOX); - - onCreate(db); - version = CUR_DATABASE_VERSION; - } - - if (dataInvalidated) { - LOGD(TAG, "Data invalidated; resetting our data timestamp."); - ConferenceDataHandler.resetDataTimestamp(mContext); - if (account != null) { - LOGI(TAG, "DB upgrade complete. Requesting resync."); - SyncHelper.requestManualSync(account); - } - } - } - - public static void deleteDatabase(Context context) { - context.deleteDatabase(DATABASE_NAME); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java deleted file mode 100644 index d33f3fabc0..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java +++ /dev/null @@ -1,1048 +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 com.google.samples.apps.iosched.provider; - -import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider; -import com.google.samples.apps.iosched.provider.ScheduleContract.Announcements; -import com.google.samples.apps.iosched.provider.ScheduleContract.PeopleIveMet; -import com.google.samples.apps.iosched.provider.ScheduleContract.Blocks; -import com.google.samples.apps.iosched.provider.ScheduleContract.Experts; -import com.google.samples.apps.iosched.provider.ScheduleContract.Feedback; -import com.google.samples.apps.iosched.provider.ScheduleContract.MapMarkers; -import com.google.samples.apps.iosched.provider.ScheduleContract.MapTiles; -import com.google.samples.apps.iosched.provider.ScheduleContract.Rooms; -import com.google.samples.apps.iosched.provider.ScheduleContract.SearchSuggest; -import com.google.samples.apps.iosched.provider.ScheduleContract.Sessions; -import com.google.samples.apps.iosched.provider.ScheduleContract.Speakers; -import com.google.samples.apps.iosched.provider.ScheduleContract.Tags; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSearchColumns; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSpeakers; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.Tables; -import com.google.samples.apps.iosched.util.SelectionBuilder; - -import android.app.Activity; -import android.app.SearchManager; -import android.content.*; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.provider.BaseColumns; -import android.text.TextUtils; -import android.util.Log; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider; -import com.google.samples.apps.iosched.provider.ScheduleContract.*; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSearchColumns; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSpeakers; -import com.google.samples.apps.iosched.provider.ScheduleDatabase.Tables; -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.samples.apps.iosched.util.SelectionBuilder; - -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.LOGV; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Provider that stores {@link ScheduleContract} data. Data is usually inserted - * by {@link com.google.samples.apps.iosched.sync.SyncHelper}, and queried by various - * {@link Activity} instances. - */ -public class ScheduleProvider extends ContentProvider { - private static final String TAG = makeLogTag(ScheduleProvider.class); - - private ScheduleDatabase mOpenHelper; - - private static final UriMatcher sUriMatcher = buildUriMatcher(); - - private static final int BLOCKS = 100; - private static final int BLOCKS_BETWEEN = 101; - private static final int BLOCKS_ID = 102; - - private static final int TAGS = 200; - private static final int TAGS_ID = 201; - - private static final int ROOMS = 300; - private static final int ROOMS_ID = 301; - private static final int ROOMS_ID_SESSIONS = 302; - - private static final int SESSIONS = 400; - private static final int SESSIONS_MY_SCHEDULE = 401; - private static final int SESSIONS_SEARCH = 403; - private static final int SESSIONS_AT = 404; - private static final int SESSIONS_ID = 405; - private static final int SESSIONS_ID_SPEAKERS = 406; - private static final int SESSIONS_ID_TAGS = 407; - private static final int SESSIONS_ROOM_AFTER = 408; - private static final int SESSIONS_UNSCHEDULED = 409; - private static final int SESSIONS_COUNTER = 410; - - private static final int SPEAKERS = 500; - private static final int SPEAKERS_ID = 501; - private static final int SPEAKERS_ID_SESSIONS = 502; - - private static final int MY_SCHEDULE = 600; - - private static final int ANNOUNCEMENTS = 700; - private static final int ANNOUNCEMENTS_ID = 701; - - private static final int SEARCH_SUGGEST = 800; - private static final int SEARCH_INDEX = 801; - - private static final int MAPMARKERS = 900; - private static final int MAPMARKERS_FLOOR = 901; - private static final int MAPMARKERS_ID = 902; - - private static final int MAPTILES = 1000; - private static final int MAPTILES_FLOOR = 1001; - - private static final int FEEDBACK_ALL = 1002; - private static final int FEEDBACK_FOR_SESSION = 1003; - - private static final int EXPERTS = 1100; - private static final int EXPERTS_ID = 1101; - private static final int HASHTAGS = 1200; - private static final int HASHTAGS_NAME = 1201; - - private static final int PEOPLE_IVE_MET = 1250; - private static final int PEOPLE_IVE_MET_ID = 1251; - - private static final int VIDEOS = 1300; - private static final int VIDEOS_ID = 1301; - - private static final int PARTNERS = 1400; - private static final int PARTNERS_ID = 1401; - - - /** - * Build and return a {@link UriMatcher} that catches all {@link Uri} - * variations supported by this {@link ContentProvider}. - */ - private static UriMatcher buildUriMatcher() { - final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); - final String authority = ScheduleContract.CONTENT_AUTHORITY; - - matcher.addURI(authority, "blocks", BLOCKS); - matcher.addURI(authority, "blocks/between/*/*", BLOCKS_BETWEEN); - matcher.addURI(authority, "blocks/*", BLOCKS_ID); - - matcher.addURI(authority, "tags", TAGS); - matcher.addURI(authority, "tags/*", TAGS_ID); - - matcher.addURI(authority, "rooms", ROOMS); - matcher.addURI(authority, "rooms/*", ROOMS_ID); - matcher.addURI(authority, "rooms/*/sessions", ROOMS_ID_SESSIONS); - - matcher.addURI(authority, "sessions", SESSIONS); - matcher.addURI(authority, "sessions/my_schedule", SESSIONS_MY_SCHEDULE); - matcher.addURI(authority, "sessions/search/*", SESSIONS_SEARCH); - matcher.addURI(authority, "sessions/at/*", SESSIONS_AT); - matcher.addURI(authority, "sessions/unscheduled/*", SESSIONS_UNSCHEDULED); - matcher.addURI(authority, "sessions/room/*/after/*", SESSIONS_ROOM_AFTER); - matcher.addURI(authority, "sessions/counter", SESSIONS_COUNTER); - matcher.addURI(authority, "sessions/*", SESSIONS_ID); - matcher.addURI(authority, "sessions/*/speakers", SESSIONS_ID_SPEAKERS); - matcher.addURI(authority, "sessions/*/tags", SESSIONS_ID_TAGS); - - matcher.addURI(authority, "my_schedule", MY_SCHEDULE); - - matcher.addURI(authority, "speakers", SPEAKERS); - matcher.addURI(authority, "speakers/*", SPEAKERS_ID); - matcher.addURI(authority, "speakers/*/sessions", SPEAKERS_ID_SESSIONS); - - matcher.addURI(authority, "announcements", ANNOUNCEMENTS); - matcher.addURI(authority, "announcements/*", ANNOUNCEMENTS_ID); - - matcher.addURI(authority, "search_suggest_query", SEARCH_SUGGEST); - matcher.addURI(authority, "search_index", SEARCH_INDEX); // 'update' only - - matcher.addURI(authority, "mapmarkers", MAPMARKERS); - matcher.addURI(authority, "mapmarkers/floor/*", MAPMARKERS_FLOOR); - matcher.addURI(authority, "mapmarkers/*", MAPMARKERS_ID); - - matcher.addURI(authority, "maptiles", MAPTILES); - matcher.addURI(authority, "maptiles/*", MAPTILES_FLOOR); - - matcher.addURI(authority, "feedback/*", FEEDBACK_FOR_SESSION); - matcher.addURI(authority, "feedback*", FEEDBACK_ALL); - matcher.addURI(authority, "feedback", FEEDBACK_ALL); - - matcher.addURI(authority, "experts/", EXPERTS); - matcher.addURI(authority, "experts/*", EXPERTS_ID); - - matcher.addURI(authority, "hashtags", HASHTAGS); - matcher.addURI(authority, "hashtags/*", HASHTAGS_NAME); - - matcher.addURI(authority, "people_ive_met/", PEOPLE_IVE_MET); - matcher.addURI(authority, "people_ive_met/*", PEOPLE_IVE_MET_ID); - - matcher.addURI(authority, "videos", VIDEOS); - matcher.addURI(authority, "videos/*", VIDEOS_ID); - - matcher.addURI(authority, "partners", PARTNERS); - matcher.addURI(authority, "partners/*", PARTNERS_ID); - - return matcher; - } - - @Override - public boolean onCreate() { - mOpenHelper = new ScheduleDatabase(getContext()); - return true; - } - - private void deleteDatabase() { - // TODO: wait for content provider operations to finish, then tear down - mOpenHelper.close(); - Context context = getContext(); - ScheduleDatabase.deleteDatabase(context); - mOpenHelper = new ScheduleDatabase(getContext()); - } - - /** {@inheritDoc} */ - @Override - public String getType(Uri uri) { - final int match = sUriMatcher.match(uri); - switch (match) { - case BLOCKS: - return Blocks.CONTENT_TYPE; - case BLOCKS_BETWEEN: - return Blocks.CONTENT_TYPE; - case BLOCKS_ID: - return Blocks.CONTENT_ITEM_TYPE; - case TAGS: - return Tags.CONTENT_TYPE; - case TAGS_ID: - return Tags.CONTENT_TYPE; - case ROOMS: - return Rooms.CONTENT_TYPE; - case ROOMS_ID: - return Rooms.CONTENT_ITEM_TYPE; - case ROOMS_ID_SESSIONS: - return Sessions.CONTENT_TYPE; - case SESSIONS: - return Sessions.CONTENT_TYPE; - case SESSIONS_MY_SCHEDULE: - return Sessions.CONTENT_TYPE; - case SESSIONS_UNSCHEDULED: - return Sessions.CONTENT_TYPE; - case SESSIONS_SEARCH: - return Sessions.CONTENT_TYPE; - case SESSIONS_AT: - return Sessions.CONTENT_TYPE; - case SESSIONS_ID: - return Sessions.CONTENT_ITEM_TYPE; - case SESSIONS_ID_SPEAKERS: - return Speakers.CONTENT_TYPE; - case SESSIONS_ID_TAGS: - return Tags.CONTENT_TYPE; - case SESSIONS_ROOM_AFTER: - return Sessions.CONTENT_TYPE; - case MY_SCHEDULE: - return MySchedule.CONTENT_TYPE; - case SPEAKERS: - return Speakers.CONTENT_TYPE; - case SPEAKERS_ID: - return Speakers.CONTENT_ITEM_TYPE; - case SPEAKERS_ID_SESSIONS: - return Sessions.CONTENT_TYPE; - case ANNOUNCEMENTS: - return Announcements.CONTENT_TYPE; - case ANNOUNCEMENTS_ID: - return Announcements.CONTENT_ITEM_TYPE; - case MAPMARKERS: - return MapMarkers.CONTENT_TYPE; - case MAPMARKERS_FLOOR: - return MapMarkers.CONTENT_TYPE; - case MAPMARKERS_ID: - return MapMarkers.CONTENT_ITEM_TYPE; - case MAPTILES: - return MapTiles.CONTENT_TYPE; - case MAPTILES_FLOOR: - return MapTiles.CONTENT_ITEM_TYPE; - case FEEDBACK_FOR_SESSION: - return Feedback.CONTENT_ITEM_TYPE; - case FEEDBACK_ALL: - return Feedback.CONTENT_TYPE; - case EXPERTS: - return Experts.CONTENT_TYPE; - case EXPERTS_ID: - return Experts.CONTENT_ITEM_TYPE; - case PEOPLE_IVE_MET: - return ScheduleContract.PeopleIveMet.CONTENT_TYPE; - case PEOPLE_IVE_MET_ID: - return ScheduleContract.PeopleIveMet.CONTENT_ITEM_TYPE; - case HASHTAGS: - return Hashtags.CONTENT_TYPE; - case HASHTAGS_NAME: - return Hashtags.CONTENT_ITEM_TYPE; - case VIDEOS: - return Videos.CONTENT_TYPE; - case VIDEOS_ID: - return Videos.CONTENT_ITEM_TYPE; - case PARTNERS: - return Partners.CONTENT_TYPE; - case PARTNERS_ID: - return Partners.CONTENT_ITEM_TYPE; - - default: - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - - /** Returns a tuple of question marks. For example, if count is 3, returns "(?,?,?)". */ - private String makeQuestionMarkTuple(int count) { - if (count < 1) { - return "()"; - } - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("(?"); - for (int i = 1; i < count; i++) { - stringBuilder.append(",?"); - } - stringBuilder.append(")"); - return stringBuilder.toString(); - } - - /** Adds the tags filter query parameter to the given builder. */ - private void addTagsFilter(SelectionBuilder builder, String tagsFilter) { - // Note: for context, remember that session queries are done on a join of sessions - // and the sessions_tags relationship table, and are GROUP'ed BY the session ID. - String[] requiredTags = tagsFilter.split(","); - if (requiredTags.length == 0) { - // filtering by 0 tags -- no-op - return; - } else if (requiredTags.length == 1) { - // filtering by only one tag, so a simple WHERE clause suffices - builder.where(Tags.TAG_ID + "=?", requiredTags[0]); - } else { - // Filtering by multiple tags, so we must add a WHERE clause with an IN operator, - // and add a HAVING statement to exclude groups that fall short of the number - // of required tags. For example, if requiredTags is { "X", "Y", "Z" }, and a certain - // session only has tags "X" and "Y", it will be excluded by the HAVING statement. - String questionMarkTuple = makeQuestionMarkTuple(requiredTags.length); - builder.where(Tags.TAG_ID + " IN " + questionMarkTuple, requiredTags); - builder.having("COUNT(" + Qualified.SESSIONS_SESSION_ID + ") >= " + requiredTags.length); - } - } - - /** {@inheritDoc} */ - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - - final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - - String tagsFilter = uri.getQueryParameter(Sessions.QUERY_PARAMETER_TAG_FILTER); - final int match = sUriMatcher.match(uri); - - // avoid the expensive string concatenation below if not loggable - if (Log.isLoggable(TAG, Log.VERBOSE)) { - LOGV(TAG, "uri=" + uri + " match=" + match + " proj=" + Arrays.toString(projection) + - " selection=" + selection + " args=" + Arrays.toString(selectionArgs) + ")"); - } - - - switch (match) { - default: { - // Most cases are handled with simple SelectionBuilder - final SelectionBuilder builder = buildExpandedSelection(uri, match); - - // If a special filter was specified, try to apply it - if (!TextUtils.isEmpty(tagsFilter)) { - addTagsFilter(builder, tagsFilter); - } - - boolean distinct = !TextUtils.isEmpty( - uri.getQueryParameter(ScheduleContract.QUERY_PARAMETER_DISTINCT)); - - Cursor cursor = builder - .where(selection, selectionArgs) - .query(db, distinct, projection, sortOrder, null); - Context context = getContext(); - if (null != context) { - cursor.setNotificationUri(context.getContentResolver(), uri); - } - return cursor; - } - case SEARCH_SUGGEST: { - final SelectionBuilder builder = new SelectionBuilder(); - - // Adjust incoming query to become SQL text match - selectionArgs[0] = selectionArgs[0] + "%"; - builder.table(Tables.SEARCH_SUGGEST); - builder.where(selection, selectionArgs); - builder.map(SearchManager.SUGGEST_COLUMN_QUERY, - SearchManager.SUGGEST_COLUMN_TEXT_1); - - projection = new String[] { - BaseColumns._ID, - SearchManager.SUGGEST_COLUMN_TEXT_1, - SearchManager.SUGGEST_COLUMN_QUERY - }; - - final String limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT); - return builder.query(db, false, projection, SearchSuggest.DEFAULT_SORT, limit); - } - } - } - - /** {@inheritDoc} */ - @Override - public Uri insert(Uri uri, ContentValues values) { - LOGV(TAG, "insert(uri=" + uri + ", values=" + values.toString() - + ", account=" + getCurrentAccountName(uri, false) + ")"); - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - final int match = sUriMatcher.match(uri); - boolean syncToNetwork = !ScheduleContract.hasCallerIsSyncAdapterParameter(uri); - switch (match) { - case BLOCKS: { - db.insertOrThrow(Tables.BLOCKS, null, values); - notifyChange(uri); - return Blocks.buildBlockUri(values.getAsString(Blocks.BLOCK_ID)); - } - case TAGS: { - db.insertOrThrow(Tables.TAGS, null, values); - notifyChange(uri); - return Tags.buildTagUri(values.getAsString(Tags.TAG_ID)); - } - case ROOMS: { - db.insertOrThrow(Tables.ROOMS, null, values); - notifyChange(uri); - return Rooms.buildRoomUri(values.getAsString(Rooms.ROOM_ID)); - } - case SESSIONS: { - db.insertOrThrow(Tables.SESSIONS, null, values); - notifyChange(uri); - return Sessions.buildSessionUri(values.getAsString(Sessions.SESSION_ID)); - } - case SESSIONS_ID_SPEAKERS: { - db.insertOrThrow(Tables.SESSIONS_SPEAKERS, null, values); - notifyChange(uri); - return Speakers.buildSpeakerUri(values.getAsString(SessionsSpeakers.SPEAKER_ID)); - } - case SESSIONS_ID_TAGS: { - db.insertOrThrow(Tables.SESSIONS_TAGS, null, values); - notifyChange(uri); - return Tags.buildTagUri(values.getAsString(Tags.TAG_ID)); - } - case MY_SCHEDULE: { - values.put(MySchedule.MY_SCHEDULE_ACCOUNT_NAME, getCurrentAccountName(uri, false)); - db.insertOrThrow(Tables.MY_SCHEDULE, null, values); - notifyChange(uri); - return Sessions.buildSessionUri(values.getAsString( - ScheduleContract.MyScheduleColumns.SESSION_ID)); - } - case SPEAKERS: { - db.insertOrThrow(Tables.SPEAKERS, null, values); - notifyChange(uri); - return Speakers.buildSpeakerUri(values.getAsString(Speakers.SPEAKER_ID)); - } - case ANNOUNCEMENTS: { - db.insertOrThrow(Tables.ANNOUNCEMENTS, null, values); - notifyChange(uri); - return Announcements.buildAnnouncementUri(values - .getAsString(Announcements.ANNOUNCEMENT_ID)); - } - case SEARCH_SUGGEST: { - db.insertOrThrow(Tables.SEARCH_SUGGEST, null, values); - notifyChange(uri); - return SearchSuggest.CONTENT_URI; - } - case MAPMARKERS: { - db.insertOrThrow(Tables.MAPMARKERS, null, values); - notifyChange(uri); - return MapMarkers.buildMarkerUri(values.getAsString(MapMarkers.MARKER_ID)); - } - case MAPTILES: { - db.insertOrThrow(Tables.MAPTILES, null, values); - notifyChange(uri); - return MapTiles.buildFloorUri(values.getAsString(MapTiles.TILE_FLOOR)); - } - case FEEDBACK_FOR_SESSION: { - db.insertOrThrow(Tables.FEEDBACK, null, values); - notifyChange(uri); - return Feedback.buildFeedbackUri(values.getAsString(Feedback.SESSION_ID)); - } - case EXPERTS: { - db.insertOrThrow(Tables.EXPERTS, null, values); - notifyChange(uri); - return Experts.buildExpertUri(values.getAsString(Experts.EXPERT_ID)); - } - case HASHTAGS: { - db.insertOrThrow(Tables.HASHTAGS, null, values); - notifyChange(uri); - return Hashtags.buildHashtagUri(values.getAsString(Hashtags.HASHTAG_NAME)); - } - case PEOPLE_IVE_MET: { - db.insertOrThrow(Tables.PEOPLE_IVE_MET, null, values); - notifyChange(uri); - return ScheduleContract.PeopleIveMet.buildPersonUri(values.getAsString(PeopleIveMet.PERSON_ID)); - } - case VIDEOS: { - db.insertOrThrow(Tables.VIDEOS, null, values); - notifyChange(uri); - return Videos.buildVideoUri(values.getAsString(Videos.VIDEO_ID)); - } - case PARTNERS: { - db.insertOrThrow(Tables.PARTNERS, null, values); - notifyChange(uri); - return Partners.buildPartnerUri(values.getAsString(Partners.PARTNER_ID)); - } - default: { - throw new UnsupportedOperationException("Unknown insert uri: " + uri); - } - } - } - - /** {@inheritDoc} */ - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - String accountName = getCurrentAccountName(uri, false); - LOGV(TAG, "update(uri=" + uri + ", values=" + values.toString() - + ", account=" + accountName + ")"); - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - final int match = sUriMatcher.match(uri); - if (match == SEARCH_INDEX) { - // update the search index - ScheduleDatabase.updateSessionSearchIndex(db); - return 1; - } - - final SelectionBuilder builder = buildSimpleSelection(uri); - if (match == MY_SCHEDULE) { - values.remove(MySchedule.MY_SCHEDULE_ACCOUNT_NAME); - builder.where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", accountName); - } - int retVal = builder.where(selection, selectionArgs).update(db, values); - notifyChange(uri); - return retVal; - } - - /** {@inheritDoc} */ - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - String accountName = getCurrentAccountName(uri, false); - LOGV(TAG, "delete(uri=" + uri + ", account=" + accountName + ")"); - if (uri == ScheduleContract.BASE_CONTENT_URI) { - // Handle whole database deletes (e.g. when signing out) - deleteDatabase(); - notifyChange(uri); - return 1; - } - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - final SelectionBuilder builder = buildSimpleSelection(uri); - final int match = sUriMatcher.match(uri); - if (match == MY_SCHEDULE) { - builder.where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", accountName); - } - int retVal = builder.where(selection, selectionArgs).delete(db); - notifyChange(uri); - return retVal; - } - - private void notifyChange(Uri uri) { - // We only notify changes if the caller is not the sync adapter. - // The sync adapter has the responsibility of notifying changes (it can do so - // more intelligently than we can -- for example, doing it only once at the end - // of the sync instead of issuing thousands of notifications for each record). - if (!ScheduleContract.hasCallerIsSyncAdapterParameter(uri)) { - Context context = getContext(); - context.getContentResolver().notifyChange(uri, null); - - // Widgets can't register content observers so we refresh widgets separately. - context.sendBroadcast(ScheduleWidgetProvider.getRefreshBroadcastIntent(context, false)); - } - } - - /** - * Apply the given set of {@link ContentProviderOperation}, executing inside - * a {@link SQLiteDatabase} transaction. All changes will be rolled back if - * any single one fails. - */ - @Override - public ContentProviderResult[] applyBatch(ArrayList operations) - throws OperationApplicationException { - final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - db.beginTransaction(); - try { - final int numOperations = operations.size(); - final ContentProviderResult[] results = new ContentProviderResult[numOperations]; - for (int i = 0; i < numOperations; i++) { - results[i] = operations.get(i).apply(this, results, i); - } - db.setTransactionSuccessful(); - return results; - } finally { - db.endTransaction(); - } - } - - /** - * Build a simple {@link SelectionBuilder} to match the requested - * {@link Uri}. This is usually enough to support {@link #insert}, - * {@link #update}, and {@link #delete} operations. - */ - private SelectionBuilder buildSimpleSelection(Uri uri) { - final SelectionBuilder builder = new SelectionBuilder(); - final int match = sUriMatcher.match(uri); - switch (match) { - case BLOCKS: { - return builder.table(Tables.BLOCKS); - } - case BLOCKS_ID: { - final String blockId = Blocks.getBlockId(uri); - return builder.table(Tables.BLOCKS) - .where(Blocks.BLOCK_ID + "=?", blockId); - } - case TAGS: { - return builder.table(Tables.TAGS); - } - case TAGS_ID: { - final String tagId = Tags.getTagId(uri); - return builder.table(Tables.TAGS) - .where(Tags.TAG_ID + "=?", tagId); - } - case ROOMS: { - return builder.table(Tables.ROOMS); - } - case ROOMS_ID: { - final String roomId = Rooms.getRoomId(uri); - return builder.table(Tables.ROOMS) - .where(Rooms.ROOM_ID + "=?", roomId); - } - case SESSIONS: { - return builder.table(Tables.SESSIONS); - } - case SESSIONS_ID: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS) - .where(Sessions.SESSION_ID + "=?", sessionId); - } - case SESSIONS_ID_SPEAKERS: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS_SPEAKERS) - .where(Sessions.SESSION_ID + "=?", sessionId); - } - case SESSIONS_ID_TAGS: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS_TAGS) - .where(Sessions.SESSION_ID + "=?", sessionId); - } - case SESSIONS_MY_SCHEDULE: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.MY_SCHEDULE) - .where(ScheduleContract.MyScheduleColumns.SESSION_ID + "=?", sessionId); - } - case MY_SCHEDULE: { - return builder.table(Tables.MY_SCHEDULE) - .where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", getCurrentAccountName(uri, false)); - } - case SPEAKERS: { - return builder.table(Tables.SPEAKERS); - } - case SPEAKERS_ID: { - final String speakerId = Speakers.getSpeakerId(uri); - return builder.table(Tables.SPEAKERS) - .where(Speakers.SPEAKER_ID + "=?", speakerId); - } - case ANNOUNCEMENTS: { - return builder.table(Tables.ANNOUNCEMENTS); - } - case ANNOUNCEMENTS_ID: { - final String announcementId = Announcements.getAnnouncementId(uri); - return builder.table(Tables.ANNOUNCEMENTS) - .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId); - } - case MAPMARKERS: { - return builder.table(Tables.MAPMARKERS); - } - case MAPMARKERS_FLOOR: { - final String floor = MapMarkers.getMarkerFloor(uri); - return builder.table(Tables.MAPMARKERS) - .where(MapMarkers.MARKER_FLOOR+ "=?", floor); - } - case MAPMARKERS_ID: { - final String markerId = MapMarkers.getMarkerId(uri); - return builder.table(Tables.MAPMARKERS) - .where(MapMarkers.MARKER_ID + "=?", markerId); - } - case MAPTILES: { - return builder.table(Tables.MAPTILES); - } - case MAPTILES_FLOOR: { - final String floor = MapTiles.getFloorId(uri); - return builder.table(Tables.MAPTILES) - .where(MapTiles.TILE_FLOOR+ "=?", floor); - } - case SEARCH_SUGGEST: { - return builder.table(Tables.SEARCH_SUGGEST); - } - case FEEDBACK_FOR_SESSION: { - final String session_id = Feedback.getSessionId(uri); - return builder.table(Tables.FEEDBACK) - .where(Feedback.SESSION_ID + "=?", session_id); - } - case FEEDBACK_ALL: { - return builder.table(Tables.FEEDBACK); - } - case EXPERTS: { - return builder.table(Tables.EXPERTS); - } - case EXPERTS_ID: { - String expertId = Experts.getExpertId(uri); - return builder.table(Tables.EXPERTS) - .where(Experts.EXPERT_ID + "= ?", expertId); - } - case HASHTAGS: { - return builder.table(Tables.HASHTAGS); - } - case HASHTAGS_NAME: { - final String hashtagName = Hashtags.getHashtagName(uri); - return builder.table(Tables.HASHTAGS) - .where(Hashtags.HASHTAG_NAME + "=?", hashtagName); - } - case PEOPLE_IVE_MET: { - return builder.table(Tables.PEOPLE_IVE_MET); - } - case PEOPLE_IVE_MET_ID: { - String personId = ScheduleContract.PeopleIveMet.getPersonId(uri); - return builder.table(Tables.PEOPLE_IVE_MET) - .where(PeopleIveMet.PERSON_ID + "=?", personId); - } - case VIDEOS: { - return builder.table(Tables.VIDEOS); - } - case VIDEOS_ID: { - final String videoId = Videos.getVideoId(uri); - return builder.table(Tables.VIDEOS).where(Videos.VIDEO_ID + "=?", videoId); - } - case PARTNERS: { - return builder.table(Tables.PARTNERS); - } - case PARTNERS_ID: { - final String partnerId = Partners.getPartnerId(uri); - return builder.table(Tables.PARTNERS).where(Partners.PARTNER_ID + "=?", partnerId); - } - default: { - throw new UnsupportedOperationException("Unknown uri for " + match + ": " + uri); - } - } - } - - private String getCurrentAccountName(Uri uri, boolean sanitize) { - String accountName = ScheduleContract.getOverrideAccountName(uri); - if (accountName == null) { - accountName = AccountUtils.getActiveAccountName(getContext()); - } - if (sanitize) { - // sanitize accountName when concatenating (http://xkcd.com/327/) - accountName = (accountName != null) ? accountName.replace("'", "''") : null; - } - return accountName; - } - - /** - * Build an advanced {@link SelectionBuilder} to match the requested - * {@link Uri}. This is usually only used by {@link #query}, since it - * performs table joins useful for {@link Cursor} data. - */ - private SelectionBuilder buildExpandedSelection(Uri uri, int match) { - final SelectionBuilder builder = new SelectionBuilder(); - switch (match) { - case BLOCKS: { - return builder.table(Tables.BLOCKS); - } - case BLOCKS_BETWEEN: { - final List segments = uri.getPathSegments(); - final String startTime = segments.get(2); - final String endTime = segments.get(3); - return builder.table(Tables.BLOCKS) - .where(Blocks.BLOCK_START + ">=?", startTime) - .where(Blocks.BLOCK_START + "<=?", endTime); - } - case BLOCKS_ID: { - final String blockId = Blocks.getBlockId(uri); - return builder.table(Tables.BLOCKS) - .where(Blocks.BLOCK_ID + "=?", blockId); - } - case TAGS: { - return builder.table(Tables.TAGS); - } - case TAGS_ID: { - final String tagId = Tags.getTagId(uri); - return builder.table(Tables.TAGS) - .where(Tags.TAG_ID + "=?", tagId); - } - case ROOMS: { - return builder.table(Tables.ROOMS); - } - case ROOMS_ID: { - final String roomId = Rooms.getRoomId(uri); - return builder.table(Tables.ROOMS) - .where(Rooms.ROOM_ID + "=?", roomId); - } - case ROOMS_ID_SESSIONS: { - final String roomId = Rooms.getRoomId(uri); - return builder.table(Tables.SESSIONS_JOIN_ROOMS) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .where(Qualified.SESSIONS_ROOM_ID + "=?", roomId); - } - case SESSIONS: { - // We query sessions on the joined table of sessions with rooms and tags. - // Since there may be more than one tag per session, we GROUP BY session ID. - // The starred sessions ("my schedule") are associated with a user, so we - // use the current user to select them properly - return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) - .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)") - .groupBy(Qualified.SESSIONS_SESSION_ID); - } - case SESSIONS_COUNTER: { - return builder.table(Tables.SESSIONS_JOIN_MYSCHEDULE, getCurrentAccountName(uri, true)) - .map(Sessions.SESSION_INTERVAL_COUNT, "count(1)") - .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)") - .groupBy(Sessions.SESSION_START + ", " + Sessions.SESSION_END); - } - case SESSIONS_MY_SCHEDULE: { - return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) - .map(Sessions.HAS_GIVEN_FEEDBACK, Subquery.SESSION_HAS_GIVEN_FEEDBACK) - .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)") - .where("( " + Sessions.SESSION_IN_MY_SCHEDULE + "=1 OR " + - Sessions.SESSION_TAGS + - " LIKE '%" + Config.Tags.SPECIAL_KEYNOTE + "%' )") - .groupBy(Qualified.SESSIONS_SESSION_ID); - } - case SESSIONS_UNSCHEDULED: { - final long[] interval = Sessions.getInterval(uri); - return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) - .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)") - .where(Sessions.SESSION_IN_MY_SCHEDULE + "=0") - .where(Sessions.SESSION_START + ">=?", String.valueOf(interval[0])) - .where(Sessions.SESSION_START + " segments = uri.getPathSegments(); - final String time = segments.get(2); - return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .where(Sessions.SESSION_START + "<=?", time) - .where(Sessions.SESSION_END + ">=?", time); - } - case SESSIONS_ID: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) - .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)") - .where(Qualified.SESSIONS_SESSION_ID + "=?", sessionId); - } - case SESSIONS_ID_SPEAKERS: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SPEAKERS) - .mapToTable(Speakers._ID, Tables.SPEAKERS) - .mapToTable(Speakers.SPEAKER_ID, Tables.SPEAKERS) - .where(Qualified.SESSIONS_SPEAKERS_SESSION_ID + "=?", sessionId); - } - case SESSIONS_ID_TAGS: { - final String sessionId = Sessions.getSessionId(uri); - return builder.table(Tables.SESSIONS_TAGS_JOIN_TAGS) - .mapToTable(Tags._ID, Tables.TAGS) - .mapToTable(Tags.TAG_ID, Tables.TAGS) - .where(Qualified.SESSIONS_TAGS_SESSION_ID + "=?", sessionId); - } - case SESSIONS_ROOM_AFTER: { - final String room = Sessions.getRoom(uri); - final String time = Sessions.getAfter(uri); - return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true)) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .where(Qualified.SESSIONS_ROOM_ID+ "=?", room) - .where("("+Sessions.SESSION_START + "<= ? AND "+Sessions.SESSION_END+ - " >= ?) OR ("+Sessions.SESSION_START + " >= ?)", time,time,time); - } - case SPEAKERS: { - return builder.table(Tables.SPEAKERS); - } - case MY_SCHEDULE: { - // force a where condition to avoid leaking schedule info to another account - // Note that, since SelectionBuilder always join multiple where calls using AND, - // even if malicious code specifying additional conditions on account_name won't - // be able to fetch data from a different account. - return builder.table(Tables.MY_SCHEDULE) - .where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", getCurrentAccountName(uri, true)); - } - case SPEAKERS_ID: { - final String speakerId = Speakers.getSpeakerId(uri); - return builder.table(Tables.SPEAKERS) - .where(Speakers.SPEAKER_ID + "=?", speakerId); - } - case SPEAKERS_ID_SESSIONS: { - final String speakerId = Speakers.getSpeakerId(uri); - return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SESSIONS_ROOMS) - .mapToTable(Sessions._ID, Tables.SESSIONS) - .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) - .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) - .where(Qualified.SESSIONS_SPEAKERS_SPEAKER_ID + "=?", speakerId); - } - case ANNOUNCEMENTS: { - return builder.table(Tables.ANNOUNCEMENTS); - } - case ANNOUNCEMENTS_ID: { - final String announcementId = Announcements.getAnnouncementId(uri); - return builder.table(Tables.ANNOUNCEMENTS) - .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId); - } - case MAPMARKERS: { - return builder.table(Tables.MAPMARKERS); - } - case MAPMARKERS_FLOOR: { - final String floor = MapMarkers.getMarkerFloor(uri); - return builder.table(Tables.MAPMARKERS) - .where(MapMarkers.MARKER_FLOOR + "=?", floor); - } - case MAPMARKERS_ID: { - final String roomId = MapMarkers.getMarkerId(uri); - return builder.table(Tables.MAPMARKERS) - .where(MapMarkers.MARKER_ID + "=?", roomId); - } - case MAPTILES: { - return builder.table(Tables.MAPTILES); - } - case MAPTILES_FLOOR: { - final String floor = MapTiles.getFloorId(uri); - return builder.table(Tables.MAPTILES) - .where(MapTiles.TILE_FLOOR + "=?", floor); - } - case FEEDBACK_FOR_SESSION: { - final String sessionId = Feedback.getSessionId(uri); - return builder.table(Tables.FEEDBACK) - - .where(Feedback.SESSION_ID + "=?", sessionId); - } - case FEEDBACK_ALL: { - return builder.table(Tables.FEEDBACK); - } - case EXPERTS: { - return builder.table(Tables.EXPERTS); - } - case EXPERTS_ID: { - String expertId = Experts.getExpertId(uri); - return builder.table(Tables.EXPERTS) - .where(Experts.EXPERT_ID + "= ?", expertId); - } - case HASHTAGS: { - return builder.table(Tables.HASHTAGS); - } - case HASHTAGS_NAME: { - final String hashtagName = Hashtags.getHashtagName(uri); - return builder.table(Tables.HASHTAGS) - .where(HashtagColumns.HASHTAG_NAME + "=?", hashtagName); - } - case PEOPLE_IVE_MET: { - return builder.table(Tables.PEOPLE_IVE_MET); - } - case PEOPLE_IVE_MET_ID: { - String personId = ScheduleContract.PeopleIveMet.getPersonId(uri); - return builder.table(Tables.PEOPLE_IVE_MET) - .where(PeopleIveMet.PERSON_ID + "=?", personId); - } - case VIDEOS: { - return builder.table(Tables.VIDEOS); - } - case VIDEOS_ID: { - final String videoId = Videos.getVideoId(uri); - return builder.table(Tables.VIDEOS) - .where(VideoColumns.VIDEO_ID + "=?", videoId); - } - case PARTNERS: { - return builder.table(Tables.PARTNERS); - } - case PARTNERS_ID: { - final String partnerId = Partners.getPartnerId(uri); - return builder.table(Tables.PARTNERS).where(Partners.PARTNER_ID + "=?", partnerId); - } - default: { - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - final int match = sUriMatcher.match(uri); - switch (match) { - default: { - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - } - - private interface Subquery { - - String SESSION_HAS_GIVEN_FEEDBACK = "(SELECT COUNT(1) FROM " - + Tables.FEEDBACK + " WHERE " + Qualified.FEEDBACK_SESSION_ID + "=" - + Qualified.SESSIONS_SESSION_ID + ")"; - String SESSIONS_SNIPPET = "snippet(" + Tables.SESSIONS_SEARCH + ",'{','}','\u2026')"; - } - - /** - * {@link ScheduleContract} fields that are fully qualified with a specific - * parent {@link Tables}. Used when needed to work around SQL ambiguity. - */ - private interface Qualified { - String SESSIONS_SESSION_ID = Tables.SESSIONS + "." + Sessions.SESSION_ID; - String SESSIONS_ROOM_ID = Tables.SESSIONS + "." + Sessions.ROOM_ID; - - String SESSIONS_TAGS_SESSION_ID = Tables.SESSIONS_TAGS + "." - + ScheduleDatabase.SessionsTags.SESSION_ID; - - String SESSIONS_SPEAKERS_SESSION_ID = Tables.SESSIONS_SPEAKERS + "." - + SessionsSpeakers.SESSION_ID; - String SESSIONS_SPEAKERS_SPEAKER_ID = Tables.SESSIONS_SPEAKERS + "." - + SessionsSpeakers.SPEAKER_ID; - String FEEDBACK_SESSION_ID = Tables.FEEDBACK + "." + Feedback.SESSION_ID; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java b/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java deleted file mode 100644 index 79f5d7bbc1..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java +++ /dev/null @@ -1,40 +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 com.google.samples.apps.iosched.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import com.google.samples.apps.iosched.service.SessionAlarmService; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * {@link BroadcastReceiver} to reinitialize {@link android.app.AlarmManager} for all starred - * session blocks. - */ -public class SessionAlarmReceiver extends BroadcastReceiver { - public static final String TAG = makeLogTag(SessionAlarmReceiver.class); - - @Override - public void onReceive(Context context, Intent intent) { - Intent scheduleIntent = new Intent( - SessionAlarmService.ACTION_SCHEDULE_ALL_STARRED_BLOCKS, - null, context, SessionAlarmService.class); - context.startService(scheduleIntent); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java b/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java deleted file mode 100644 index 3c15dec5bb..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java +++ /dev/null @@ -1,218 +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 com.google.samples.apps.iosched.service; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.wearable.DataApi; -import com.google.android.gms.wearable.DataEvent; -import com.google.android.gms.wearable.DataEventBuffer; -import com.google.android.gms.wearable.DataMap; -import com.google.android.gms.wearable.DataMapItem; -import com.google.android.gms.wearable.PutDataMapRequest; -import com.google.android.gms.wearable.Wearable; -import com.google.android.gms.wearable.WearableListenerService; -import com.google.samples.apps.iosched.util.FeedbackUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.app.Service; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.NotificationManagerCompat; -import android.text.TextUtils; -import android.util.Log; - -import java.util.concurrent.TimeUnit; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.LOGE; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * A {@link com.google.android.gms.wearable.WearableListenerService} service to receive the session - * feedback from the wearable device and handle dismissal of notifications by deleting the - * associated Data Items. - */ -public class FeedbackListenerService extends WearableListenerService - implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { - - private static final String TAG = makeLogTag(FeedbackListenerService.class); - public static final String PATH_RESPONSE = "/iowear/response"; - private GoogleApiClient mGoogleApiClient; - private boolean mConnected = false; - private static final long TIMEOUT_S = 10; // how long wait for APi Client connection, in seconds - - @Override - public void onCreate() { - super.onCreate(); - mGoogleApiClient = new GoogleApiClient.Builder(this) - .addApi(Wearable.API) - .addConnectionCallbacks(this) - .addOnConnectionFailedListener(this) - .build(); - mGoogleApiClient.connect(); - } - - public int onStartCommand(Intent intent, int flags, int startId) { - if (null != intent) { - String action = intent.getAction(); - if (SessionAlarmService.ACTION_NOTIFICATION_DISMISSAL.equals(action)) { - String sessionId = intent.getStringExtra(SessionAlarmService.KEY_SESSION_ID); - LOGD(TAG, "onStartCommand(): Action = ACTION_NOTIFICATION_DISMISSAL Session: " - + sessionId); - dismissWearableNotification(sessionId); - } - } - return Service.START_NOT_STICKY; - } - - /** - * Removes the Data Item that was used to create a notification on the watch. By deleting the - * data item, a {@link com.google.android.gms.wearable.WearableListenerService} on the watch - * will be notified and the notification on the watch will be removed. - *

- * Since connection to the Google API client is asynchronous, we spawn a thread and wait for - * the connection to be established before attempting to use the Google API client. - * - * @param sessionId The Session ID of the notification that should be removed - */ - private void dismissWearableNotification(final String sessionId) { - new Thread(new Runnable() { - @Override - public void run() { - if (!mConnected) { - mGoogleApiClient.blockingConnect(TIMEOUT_S, TimeUnit.SECONDS); - } - if (!mConnected) { - Log.e(TAG, "Failed to connect to mGoogleApiClient within " + TIMEOUT_S - + " seconds"); - return; - } - LOGD(TAG, "dismissWearableNotification(): Attempting to dismiss wearable " - + "notification"); - PutDataMapRequest putDataMapRequest = PutDataMapRequest - .create(FeedbackUtils.getFeedbackPath(sessionId)); - if (mGoogleApiClient.isConnected()) { - Wearable.DataApi.deleteDataItems(mGoogleApiClient, putDataMapRequest.getUri()) - .setResultCallback(new ResultCallback() { - @Override - public void onResult( - DataApi.DeleteDataItemsResult deleteDataItemsResult) { - if (!deleteDataItemsResult.getStatus().isSuccess()) { - LOGD(TAG, "dismissWearableNotification(): failed to delete" - + " the data item"); - } - } - }); - } else { - Log.e(TAG, "dismissWearableNotification()): No Google API Client connection"); - } - } - }).start(); - } - - @Override - public void onDataChanged(DataEventBuffer dataEvents) { - LOGD(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName()); - - for (DataEvent event : dataEvents) { - LOGD(TAG, "Uri is: " + event.getDataItem().getUri()); - DataMapItem mapItem = DataMapItem.fromDataItem(event.getDataItem()); - String path = event.getDataItem().getUri().getPath(); - if (event.getType() == DataEvent.TYPE_CHANGED) { - if (PATH_RESPONSE.equals(path)) { - // we have a response - DataMap data = mapItem.getDataMap(); - String jsonString = data.getString("response"); - if (TextUtils.isEmpty(jsonString)) { - return; - } - LOGD(TAG, "jsonString is: " + jsonString); - saveFeedback(jsonString); - } - } else if (event.getType() == DataEvent.TYPE_DELETED) { - if (path.startsWith(SessionAlarmService.PATH_FEEDBACK)) { - Uri uri = event.getDataItem().getUri(); - dismissLocalNotification(uri.getLastPathSegment()); - } - } - } - } - - /** - * Dismisses the local notification for the given session - */ - private void dismissLocalNotification(String sessionId) { - LOGD(TAG, "dismissLocalNotification: sessionId=" + sessionId); - NotificationManagerCompat.from(this) - .cancel(sessionId, SessionAlarmService.FEEDBACK_NOTIFICATION_ID); - } - - /** - * Persisting the feedback in the database. The input is the JSON string that represents the - * response from the user on the paired wear device. The format of a typical response is: - *

[{"s":"sessionId-1234"},{"q":1,"a":2},{"q":0,"a":1},{"q":3,"a":1},{"q":2,"a":1}]
- */ - private void saveFeedback(String jsonString) { - try { - JSONArray jsonArray = new JSONArray(jsonString); - if (null != jsonArray) { - JSONObject sessionObj = (JSONObject) jsonArray.get(0); - String sessionId = sessionObj.getString("s"); - StringBuffer result = new StringBuffer("Session Id: " + sessionId + "\n"); - int[] answers = new int[4]; - for (int i = 0; i < answers.length; i++) { - answers[i] = -1; - } - for (int i = 1; i < jsonArray.length(); i++) { - JSONObject answerObj = (JSONObject) jsonArray.get(i); - int question = answerObj.getInt("q"); - int answer = answerObj.getInt("a") + 1; - answers[question] = answer; - result.append("Question: " + question + " ---> Answer: " + answer + "\n"); - } - LOGD(TAG, "Feedback answers received from the wear: " + result.toString()); - FeedbackUtils.saveSessionFeedback(this, sessionId, answers[0], answers[1], - answers[2], answers[3], null); - } - - } catch (JSONException e) { - LOGE(TAG, "Failed to parse the json received from the wear", e); - } - } - - @Override - public void onConnected(Bundle bundle) { - mConnected = true; - } - - @Override - public void onConnectionSuspended(int i) { - mConnected = false; - } - - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - Log.e(TAG, "Failed to connect to the Google API client"); - mConnected = false; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java b/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java deleted file mode 100644 index 948e97f88f..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java +++ /dev/null @@ -1,669 +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 com.google.samples.apps.iosched.service; - -import android.app.*; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.TaskStackBuilder; -import android.util.Log; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.wearable.DataApi; -import com.google.android.gms.wearable.PutDataMapRequest; -import com.google.android.gms.wearable.PutDataRequest; -import com.google.android.gms.wearable.Wearable; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.ui.BrowseSessionsActivity; -import com.google.samples.apps.iosched.ui.MapFragment; -import com.google.samples.apps.iosched.ui.MyScheduleActivity; -import com.google.samples.apps.iosched.ui.SessionFeedbackActivity; -import com.google.samples.apps.iosched.ui.phone.MapActivity; -import com.google.samples.apps.iosched.util.FeedbackUtils; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.UIUtils; - -import java.util.ArrayList; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Background service to handle scheduling of starred session notification via - * {@link android.app.AlarmManager}. - */ -public class SessionAlarmService extends IntentService - implements GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - private static final String TAG = makeLogTag(SessionAlarmService.class); - - public static final String ACTION_NOTIFY_SESSION = - "com.google.samples.apps.iosched.action.NOTIFY_SESSION"; - public static final String ACTION_NOTIFY_SESSION_FEEDBACK = - "com.google.samples.apps.iosched.action.NOTIFY_SESSION_FEEDBACK"; - public static final String ACTION_SCHEDULE_FEEDBACK_NOTIFICATION = - "com.google.samples.apps.iosched.action.SCHEDULE_FEEDBACK_NOTIFICATION"; - public static final String ACTION_SCHEDULE_STARRED_BLOCK = - "com.google.samples.apps.iosched.action.SCHEDULE_STARRED_BLOCK"; - public static final String ACTION_SCHEDULE_ALL_STARRED_BLOCKS = - "com.google.samples.apps.iosched.action.SCHEDULE_ALL_STARRED_BLOCKS"; - public static final String EXTRA_SESSION_START = - "com.google.samples.apps.iosched.extra.SESSION_START"; - public static final String EXTRA_SESSION_END = - "com.google.samples.apps.iosched.extra.SESSION_END"; - public static final String EXTRA_SESSION_ALARM_OFFSET = - "com.google.samples.apps.iosched.extra.SESSION_ALARM_OFFSET"; - public static final String EXTRA_SESSION_ID = - "com.google.samples.apps.iosched.extra.SESSION_ID"; - public static final String EXTRA_SESSION_TITLE = - "com.google.samples.apps.iosched.extra.SESSION_TITLE"; - public static final String EXTRA_SESSION_ROOM = - "com.google.samples.apps.iosched.extra.SESSION_ROOM"; - public static final String EXTRA_SESSION_SPEAKERS = - "com.google.samples.apps.iosched.extra.SESSION_SPEAKERS"; - - public static final int NOTIFICATION_ID = 100; - public static final int FEEDBACK_NOTIFICATION_ID = 101; - - // pulsate every 1 second, indicating a relatively high degree of urgency - private static final int NOTIFICATION_LED_ON_MS = 100; - private static final int NOTIFICATION_LED_OFF_MS = 1000; - private static final int NOTIFICATION_ARGB_COLOR = 0xff0088ff; // cyan - - private static final long MILLI_TEN_MINUTES = 600000; - private static final long MILLI_FIVE_MINUTES = 300000; - private static final long MILLI_ONE_MINUTE = 60000; - - private static final long UNDEFINED_ALARM_OFFSET = -1; - private static final long UNDEFINED_VALUE = -1; - public static final String ACTION_NOTIFICATION_DISMISSAL - = "com.google.sample.apps.iosched.ACTION_NOTIFICATION_DISMISSAL"; - private GoogleApiClient mGoogleApiClient; - public static final String KEY_SESSION_ID = "session-id"; - private static final String KEY_SESSION_NAME = "session-name"; - private static final String KEY_SPEAKER_NAME = "speaker-name"; - private static final String KEY_SESSION_ROOM = "session-room"; - public static final String PATH_FEEDBACK = "/iowear/feedback"; - - // special session ID that identifies a debug notification - public static final String DEBUG_SESSION_ID = "debug-session-id"; - - public SessionAlarmService() { - super(TAG); - } - - @Override - public void onCreate() { - super.onCreate(); - mGoogleApiClient = new GoogleApiClient.Builder(this) - .addApi(Wearable.API) - .addConnectionCallbacks(this) - .addOnConnectionFailedListener(this) - .build(); - } - - @Override - protected void onHandleIntent(Intent intent) { - mGoogleApiClient.blockingConnect(2000, TimeUnit.MILLISECONDS); - final String action = intent.getAction(); - - LOGD(TAG, "SessionAlarmService handling " + action); - - if (ACTION_SCHEDULE_ALL_STARRED_BLOCKS.equals(action)) { - LOGD(TAG, "Scheduling all starred blocks."); - scheduleAllStarredBlocks(); - scheduleAllStarredSessionFeedbacks(); - return; - } - - final long sessionEnd = intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_END, - UNDEFINED_VALUE); - if (sessionEnd == UNDEFINED_VALUE) { - LOGD(TAG, "IGNORING ACTION -- missing sessionEnd parameter"); - return; - } - - final long sessionAlarmOffset = - intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, - UNDEFINED_ALARM_OFFSET); - LOGD(TAG, "Session alarm offset is: " + sessionAlarmOffset); - - // Feedback notifications have a slightly different set of extras. - if (ACTION_SCHEDULE_FEEDBACK_NOTIFICATION.equals(action) || - ACTION_NOTIFY_SESSION_FEEDBACK.equals(action)) { - final String sessionId = intent.getStringExtra(SessionAlarmService.EXTRA_SESSION_ID); - final String sessionTitle = intent.getStringExtra( - SessionAlarmService.EXTRA_SESSION_TITLE); - final String sessionRoom = intent.getStringExtra( - SessionAlarmService.EXTRA_SESSION_ROOM); - final String sessionSpeakers = intent.getStringExtra( - SessionAlarmService.EXTRA_SESSION_SPEAKERS); - if (sessionTitle == null || sessionEnd == UNDEFINED_VALUE || - sessionId == null) { - Log.e(TAG, - "Attempted to schedule or notify for feedback without providing extras."); - return; - } - if (ACTION_SCHEDULE_FEEDBACK_NOTIFICATION.equals(action)) { - LOGD(TAG, "Scheduling feedback alarm for session: " + sessionTitle); - scheduleFeedbackAlarm(sessionId, sessionEnd, sessionAlarmOffset, sessionTitle, - sessionRoom, sessionSpeakers); - } else { - LOGD(TAG, "Notifying for feedback on session: " + sessionTitle); - notifySessionFeedback(sessionId, sessionEnd, sessionTitle, sessionRoom, - sessionSpeakers); - } - return; - } - - final long sessionStart = - intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_START, UNDEFINED_VALUE); - if (sessionStart == UNDEFINED_VALUE) { - LOGD(TAG, "IGNORING ACTION -- no session start parameter."); - return; - } - - if (ACTION_NOTIFY_SESSION.equals(action)) { - LOGD(TAG, "Notifying about sessions starting at " + - sessionStart + " = " + (new Date(sessionStart)).toString()); - LOGD(TAG, "-> Alarm offset: " + sessionAlarmOffset); - notifySession(sessionStart, sessionAlarmOffset); - } else if (ACTION_SCHEDULE_STARRED_BLOCK.equals(action)) { - LOGD(TAG, "Scheduling session alarm."); - LOGD(TAG, "-> Session start: " + sessionStart + " = " + (new Date(sessionStart)) - .toString()); - LOGD(TAG, "-> Session end: " + sessionEnd + " = " + (new Date(sessionEnd)).toString()); - LOGD(TAG, "-> Alarm offset: " + sessionAlarmOffset); - scheduleAlarm(sessionStart, sessionEnd, sessionAlarmOffset); - } - } - - public void scheduleFeedbackAlarm(final String sessionId, final long sessionEnd, - final long alarmOffset, final String sessionTitle, String sessionRoom, - String sessionSpeakers) { - // By default, feedback alarms fire 5 minutes before session end time. If alarm offset is - // provided, alarm is set to go off that much time from now (useful for testing). - long alarmTime; - if (alarmOffset == UNDEFINED_ALARM_OFFSET) { - alarmTime = sessionEnd - MILLI_FIVE_MINUTES; - } else { - alarmTime = UIUtils.getCurrentTime(this) + alarmOffset; - } - - LOGD(TAG, "Scheduling session feedback alarm for session '" + sessionTitle + "'"); - LOGD(TAG, " -> end time: " + sessionEnd + " = " + (new Date(sessionEnd)).toString()); - LOGD(TAG, " -> alarm time: " + alarmTime + " = " + (new Date(alarmTime)).toString()); - LOGD(TAG, " -> room name: " + sessionRoom); - LOGD(TAG, " -> speakers: " + sessionSpeakers); - - final Intent feedbackIntent = new Intent( - ACTION_NOTIFY_SESSION_FEEDBACK, - null, - this, - SessionAlarmService.class); - feedbackIntent.setData( - new Uri.Builder().authority("com.google.samples.apps.iosched") - .path(sessionId).build() - ); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, alarmOffset); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ID, sessionId); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_TITLE, sessionTitle); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_SPEAKERS, sessionSpeakers); - feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ROOM, sessionRoom); - PendingIntent pi = PendingIntent.getService( - this, 1, feedbackIntent, PendingIntent.FLAG_CANCEL_CURRENT); - final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - am.set(AlarmManager.RTC_WAKEUP, alarmTime, pi); - } - - private void scheduleAlarm(final long sessionStart, - final long sessionEnd, final long alarmOffset) { - - NotificationManager nm = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); - final long currentTime = UIUtils.getCurrentTime(this); - // If the session is already started, do not schedule system notification. - if (currentTime > sessionStart) { - LOGD(TAG, "Not scheduling alarm because target time is in the past: " + sessionStart); - return; - } - - // By default, sets alarm to go off at 10 minutes before session start time. If alarm - // offset is provided, alarm is set to go off by that much time from now. - long alarmTime; - if (alarmOffset == UNDEFINED_ALARM_OFFSET) { - alarmTime = sessionStart - MILLI_TEN_MINUTES; - } else { - alarmTime = currentTime + alarmOffset; - } - - LOGD(TAG, "Scheduling alarm for " + alarmTime + " = " + (new Date(alarmTime)).toString()); - - final Intent notifIntent = new Intent( - ACTION_NOTIFY_SESSION, - null, - this, - SessionAlarmService.class); - // Setting data to ensure intent's uniqueness for different session start times. - notifIntent.setData( - new Uri.Builder().authority("com.google.samples.apps.iosched") - .path(String.valueOf(sessionStart)).build() - ); - notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart); - LOGD(TAG, "-> Intent extra: session start " + sessionStart); - notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd); - LOGD(TAG, "-> Intent extra: session end " + sessionEnd); - notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, alarmOffset); - LOGD(TAG, "-> Intent extra: session alarm offset " + alarmOffset); - PendingIntent pi = PendingIntent.getService(this, - 0, - notifIntent, - PendingIntent.FLAG_CANCEL_CURRENT); - final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - // Schedule an alarm to be fired to notify user of added sessions are about to begin. - LOGD(TAG, "-> Scheduling RTC_WAKEUP alarm at " + alarmTime); - am.set(AlarmManager.RTC_WAKEUP, alarmTime, pi); - } - - // A starred session is about to end; notify the user to provide session feedback. - // Constructs and triggers a system notification. Does nothing if the session has already - // concluded. - private void notifySessionFeedback(final String sessionId, final long sessionEnd, - final String sessionTitle, final String sessionRoom, final String sessionSpeakers) { - LOGD(TAG, "Considering firing notification for feedback for session: " + sessionTitle); - boolean isDebug = DEBUG_SESSION_ID.equals(sessionId); - - if (isDebug) { - LOGD(TAG, "Note: this is a debug notification."); - } - - // Don't fire notification if this feature is disabled in settings - if (!PrefUtils.shouldShowSessionFeedbackReminders(this)) { - LOGD(TAG, "Skipping session feedback notification for session " + sessionId + " (" - + sessionTitle + "). Disabled in settings."); - return; - } - - // Avoid repeated notifications. - if (!isDebug && UIUtils.isFeedbackNotificationFiredForSession(this, sessionId)) { - LOGD(TAG, "Skipping repeated session feedback notification for session '" - + sessionTitle + "'"); - return; - } - - // If the session is no longer is MY_SCHEDULE, don't notify for it. - final Uri myScheduleUri = ScheduleContract.MySchedule.buildMyScheduleUri(this); - final Cursor c = getContentResolver().query( - myScheduleUri, MySessionsExistenceQuery.PROJECTION, - MySessionsExistenceQuery.WHERE_CLAUSE, new String[]{sessionId}, null); - if (!isDebug && (c == null || !c.moveToFirst())) { - // no longer in MY_SCHEDULE - return; - } - - LOGD(TAG, "Going forward with session feedback notification for: " + sessionTitle); - final Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(sessionId); - - final Resources res = getResources(); - String contentText = res.getString(R.string.session_feedback_notification_text, - sessionTitle); - - PendingIntent pi = TaskStackBuilder.create(this) - .addNextIntent(new Intent(this, MyScheduleActivity.class)) - .addNextIntent(new Intent(Intent.ACTION_VIEW, sessionUri, this, - SessionFeedbackActivity.class)) - .getPendingIntent(1, PendingIntent.FLAG_CANCEL_CURRENT); - - // this is used to synchronize deletion of notifications on phone and wear - Intent dismissalIntent = new Intent(ACTION_NOTIFICATION_DISMISSAL); - dismissalIntent.putExtra(KEY_SESSION_ID, sessionId); - PendingIntent dismissalPendingIntent = PendingIntent - .getService(this, (int) new Date().getTime(), dismissalIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this) - .setContentTitle(sessionTitle) - .setContentText(contentText) - //.setColor(getResources().getColor(R.color.theme_primary)) - // Note: setColor() is available in the support lib v21+. - // We commented it out because we want the source to compile - // against support lib v20. If you are using support lib - // v21 or above on Android L, uncomment this line. - .setTicker(res.getString(R.string.session_feedback_notification_ticker)) - .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE) - .setLights( - SessionAlarmService.NOTIFICATION_ARGB_COLOR, - SessionAlarmService.NOTIFICATION_LED_ON_MS, - SessionAlarmService.NOTIFICATION_LED_OFF_MS) - .setSmallIcon(R.drawable.ic_stat_notification) - .setContentIntent(pi) - .setPriority(Notification.PRIORITY_MAX) - .setLocalOnly(true) // make it local to the phone - .setDeleteIntent(dismissalPendingIntent) - .setAutoCancel(true); - NotificationManager nm = (NotificationManager) getSystemService( - Context.NOTIFICATION_SERVICE); - LOGD(TAG, "Now showing session feedback notification!"); - nm.notify(sessionId, FEEDBACK_NOTIFICATION_ID, notifBuilder.build()); - setupNotificationOnWear(sessionId, sessionRoom, sessionTitle, sessionSpeakers); - } - - /** - * Builds corresponding notification for the Wear device that is paired to this handset. This - * is done by adding a Data Item to teh Data Store; the Wear device will be notified to build a - * local notification. - */ - private void setupNotificationOnWear(String sessionId, String sessionRoom, String sessionName, - String speaker) { - if (!mGoogleApiClient.isConnected()) { - Log.e(TAG, "setupNotificationOnWear(): Failed to send data item since there was no " - + "connectivity to Google API Client"); - return; - } - PutDataMapRequest putDataMapRequest = PutDataMapRequest - .create(FeedbackUtils.getFeedbackPath(sessionId)); - putDataMapRequest.getDataMap().putLong("time", new Date().getTime()); - putDataMapRequest.getDataMap().putString(KEY_SESSION_ID, sessionId); - putDataMapRequest.getDataMap().putString(KEY_SESSION_NAME, sessionName); - putDataMapRequest.getDataMap().putString(KEY_SPEAKER_NAME, speaker); - putDataMapRequest.getDataMap().putString(KEY_SESSION_ROOM, sessionRoom); - - PutDataRequest request = putDataMapRequest.asPutDataRequest(); - - Wearable.DataApi.putDataItem(mGoogleApiClient, request) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(DataApi.DataItemResult dataItemResult) { - LOGD(TAG, "setupNotificationOnWear(): Sending notification result success:" - + dataItemResult.getStatus().isSuccess() - ); - } - }); - } - - // Starred sessions are about to begin. Constructs and triggers system notification. - private void notifySession(final long sessionStart, final long alarmOffset) { - long currentTime = UIUtils.getCurrentTime(this); - final long intervalEnd = sessionStart + MILLI_TEN_MINUTES; - LOGD(TAG, "Considering notifying for time interval."); - LOGD(TAG, " Interval start: " + sessionStart + "=" + (new Date(sessionStart)).toString()); - LOGD(TAG, " Interval end: " + intervalEnd + "=" + (new Date(intervalEnd)).toString()); - LOGD(TAG, " Current time is: " + currentTime + "=" + (new Date(currentTime)).toString()); - if (sessionStart < currentTime) { - LOGD(TAG, "Skipping session notification (too late -- time interval already started)"); - return; - } - - if (!PrefUtils.shouldShowSessionReminders(this)) { - // skip if disabled in settings - LOGD(TAG, "Skipping session notification for sessions. Disabled in settings."); - return; - } - - // Avoid repeated notifications. - if (alarmOffset == UNDEFINED_ALARM_OFFSET && UIUtils.isNotificationFiredForBlock( - this, ScheduleContract.Blocks.generateBlockId(sessionStart, intervalEnd))) { - LOGD(TAG, "Skipping session notification (already notified)"); - return; - } - - final ContentResolver cr = getContentResolver(); - - LOGD(TAG, "Looking for sessions in interval " + sessionStart + " - " + intervalEnd); - Cursor c = cr.query( - ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI, - SessionDetailQuery.PROJECTION, - ScheduleContract.Sessions.STARTING_AT_TIME_INTERVAL_SELECTION, - ScheduleContract.Sessions.buildAtTimeIntervalArgs(sessionStart, intervalEnd), - null); - int starredCount = c.getCount(); - LOGD(TAG, "# starred sessions in that interval: " + c.getCount()); - String singleSessionId = null; - String singleSessionRoomId = null; - ArrayList starredSessionTitles = new ArrayList(); - while (c.moveToNext()) { - singleSessionId = c.getString(SessionDetailQuery.SESSION_ID); - singleSessionRoomId = c.getString(SessionDetailQuery.ROOM_ID); - starredSessionTitles.add(c.getString(SessionDetailQuery.SESSION_TITLE)); - LOGD(TAG, "-> Title: " + c.getString(SessionDetailQuery.SESSION_TITLE)); - } - if (starredCount < 1) { - return; - } - - // Generates the pending intent which gets fired when the user taps on the notification. - // NOTE: Use TaskStackBuilder to comply with Android's design guidelines - // related to navigation from notifications. - Intent baseIntent = new Intent(this, MyScheduleActivity.class); - baseIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - TaskStackBuilder taskBuilder = TaskStackBuilder.create(this) - .addNextIntent(baseIntent); - - // For a single session, tapping the notification should open the session details (b/15350787) - if (starredCount == 1) { - taskBuilder.addNextIntent(new Intent(Intent.ACTION_VIEW, - ScheduleContract.Sessions.buildSessionUri(singleSessionId))); - } - - PendingIntent pi = taskBuilder.getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT); - - final Resources res = getResources(); - String contentText; - int minutesLeft = (int) (sessionStart - currentTime + 59000) / 60000; - if (minutesLeft < 1) { - minutesLeft = 1; - } - - if (starredCount == 1) { - contentText = res.getString(R.string.session_notification_text_1, minutesLeft); - } else { - contentText = res.getQuantityString(R.plurals.session_notification_text, - starredCount - 1, - minutesLeft, - starredCount - 1); - } - - NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this) - .setContentTitle(starredSessionTitles.get(0)) - .setContentText(contentText) - //.setColor(getResources().getColor(R.color.theme_primary)) - // Note: setColor() is available in the support lib v21+. - // We commented it out because we want the source to compile - // against support lib v20. If you are using support lib - // v21 or above on Android L, uncomment this line. - .setTicker(res.getQuantityString(R.plurals.session_notification_ticker, - starredCount, - starredCount)) - .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE) - .setLights( - SessionAlarmService.NOTIFICATION_ARGB_COLOR, - SessionAlarmService.NOTIFICATION_LED_ON_MS, - SessionAlarmService.NOTIFICATION_LED_OFF_MS) - .setSmallIcon(R.drawable.ic_stat_notification) - .setContentIntent(pi) - .setPriority(Notification.PRIORITY_MAX) - .setAutoCancel(true); - if (minutesLeft > 5) { - notifBuilder.addAction(R.drawable.ic_alarm_holo_dark, - String.format(res.getString(R.string.snooze_x_min), 5), - createSnoozeIntent(sessionStart, intervalEnd, 5)); - } - if (starredCount == 1 && PrefUtils.isAttendeeAtVenue(this)) { - notifBuilder.addAction(R.drawable.ic_map_holo_dark, - res.getString(R.string.title_map), - createRoomMapIntent(singleSessionRoomId)); - } - String bigContentTitle; - if (starredCount == 1 && starredSessionTitles.size() > 0) { - bigContentTitle = starredSessionTitles.get(0); - } else { - bigContentTitle = res.getQuantityString(R.plurals.session_notification_title, - starredCount, - minutesLeft, - starredCount); - } - NotificationCompat.InboxStyle richNotification = new NotificationCompat.InboxStyle( - notifBuilder) - .setBigContentTitle(bigContentTitle); - - // Adds starred sessions starting at this time block to the notification. - for (int i = 0; i < starredCount; i++) { - richNotification.addLine(starredSessionTitles.get(i)); - } - NotificationManager nm = (NotificationManager) getSystemService( - Context.NOTIFICATION_SERVICE); - LOGD(TAG, "Now showing notification."); - nm.notify(NOTIFICATION_ID, richNotification.build()); - } - - private PendingIntent createSnoozeIntent(final long sessionStart, final long sessionEnd, - final int snoozeMinutes) { - Intent scheduleIntent = new Intent( - SessionAlarmService.ACTION_SCHEDULE_STARRED_BLOCK, - null, this, SessionAlarmService.class); - scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart); - scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd); - scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, - snoozeMinutes * MILLI_ONE_MINUTE); - return PendingIntent.getService(this, 0, scheduleIntent, - PendingIntent.FLAG_CANCEL_CURRENT); - } - - private PendingIntent createRoomMapIntent(final String roomId) { - Intent mapIntent = new Intent(getApplicationContext(), - UIUtils.getMapActivityClass(getApplicationContext())); - mapIntent.putExtra(MapFragment.EXTRA_ROOM, roomId); - mapIntent.putExtra(MapActivity.EXTRA_DETACHED_MODE, true); - return TaskStackBuilder - .create(getApplicationContext()) - .addNextIntent(new Intent(this, BrowseSessionsActivity.class)) - .addNextIntent(mapIntent) - .getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT); - } - - private void scheduleAllStarredBlocks() { - final ContentResolver cr = getContentResolver(); - final Cursor c = cr.query(ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI, - new String[]{"distinct " + ScheduleContract.Sessions.SESSION_START, - ScheduleContract.Sessions.SESSION_END, - ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE}, - null, - null, - null - ); - if (c == null) { - return; - } - - while (c.moveToNext()) { - final long sessionStart = c.getLong(0); - final long sessionEnd = c.getLong(1); - scheduleAlarm(sessionStart, sessionEnd, UNDEFINED_ALARM_OFFSET); - } - } - - // Schedules feedback alarms for all starred sessions. - private void scheduleAllStarredSessionFeedbacks() { - final ContentResolver cr = getContentResolver(); - // TODO: Should we also check that SESSION_IN_MY_SCHEDULE is true? - final Cursor c = cr.query(ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI, - new String[]{ - ScheduleContract.Sessions.SESSION_ID, - ScheduleContract.Sessions.SESSION_TITLE, - ScheduleContract.Sessions.SESSION_END, - ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE, - ScheduleContract.Sessions.ROOM_NAME, - ScheduleContract.Sessions.SESSION_SPEAKER_NAMES, - }, - null, - null, - null - ); - if (c == null) { - return; - } - while (c.moveToNext()) { - final String sessionId = c.getString(0); - final String sessionTitle = c.getString(1); - final long sessionEnd = c.getLong(2); - final String sessionRoom = c.getString(3); - final String sessionSpeakers = c.getString(4); - scheduleFeedbackAlarm(sessionId, sessionEnd, UNDEFINED_ALARM_OFFSET, sessionTitle, - sessionRoom, sessionSpeakers); - } - } - - public interface SessionDetailQuery { - - String[] PROJECTION = { - ScheduleContract.Sessions.SESSION_ID, - ScheduleContract.Sessions.SESSION_TITLE, - ScheduleContract.Sessions.ROOM_ID, - ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE - }; - - int SESSION_ID = 0; - int SESSION_TITLE = 1; - int ROOM_ID = 2; - } - - public interface MySessionsExistenceQuery { - - String[] PROJECTION = { - ScheduleContract.MySchedule.SESSION_ID - }; - - int SESSION_ID = 0; - - public static final String WHERE_CLAUSE = - ScheduleContract.MySchedule.SESSION_ID + "=?"; - } - - @Override - public void onConnected(Bundle connectionHint) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Connected to Google Api Service"); - } - } - - @Override - public void onConnectionSuspended(int cause) { - // Ignore - } - - @Override - public void onConnectionFailed(ConnectionResult result) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Disconnected from Google Api Service"); - } - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java b/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java deleted file mode 100644 index f523cf26a6..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java +++ /dev/null @@ -1,421 +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 com.google.samples.apps.iosched.service; - -import android.util.Log; -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.util.AccountUtils; - -import android.annotation.TargetApi; -import android.app.IntentService; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.provider.CalendarContract; -import android.text.TextUtils; -import com.google.samples.apps.iosched.util.PrefUtils; - -import java.util.ArrayList; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGE; -import static com.google.samples.apps.iosched.util.LogUtils.LOGW; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Background {@link android.app.Service} that adds or removes session Calendar events through - * the {@link CalendarContract} API available in Android 4.0 or above. - */ -public class SessionCalendarService extends IntentService { - private static final String TAG = makeLogTag(SessionCalendarService.class); - - public static final String ACTION_ADD_SESSION_CALENDAR = - "com.google.samples.apps.iosched.action.ADD_SESSION_CALENDAR"; - public static final String ACTION_REMOVE_SESSION_CALENDAR = - "com.google.samples.apps.iosched.action.REMOVE_SESSION_CALENDAR"; - public static final String ACTION_UPDATE_ALL_SESSIONS_CALENDAR = - "com.google.samples.apps.iosched.action.UPDATE_ALL_SESSIONS_CALENDAR"; - public static final String ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED = - "com.google.samples.apps.iosched.action.UPDATE_CALENDAR_COMPLETED"; - public static final String ACTION_CLEAR_ALL_SESSIONS_CALENDAR = - "com.google.samples.apps.iosched.action.CLEAR_ALL_SESSIONS_CALENDAR"; - public static final String EXTRA_ACCOUNT_NAME = - "com.google.samples.apps.iosched.extra.ACCOUNT_NAME"; - public static final String EXTRA_SESSION_START = - "com.google.samples.apps.iosched.extra.SESSION_BLOCK_START"; - public static final String EXTRA_SESSION_END = - "com.google.samples.apps.iosched.extra.SESSION_BLOCK_END"; - public static final String EXTRA_SESSION_TITLE = - "com.google.samples.apps.iosched.extra.SESSION_TITLE"; - public static final String EXTRA_SESSION_ROOM = - "com.google.samples.apps.iosched.extra.SESSION_ROOM"; - - private static final long INVALID_CALENDAR_ID = -1; - - // TODO: localize - private static final String CALENDAR_CLEAR_SEARCH_LIKE_EXPRESSION = - "%added by Google I/O Android app%"; - - public SessionCalendarService() { - super(TAG); - } - - @Override - protected void onHandleIntent(Intent intent) { - final String action = intent.getAction(); - Log.d(TAG, "Received intent: " + action); - - final ContentResolver resolver = getContentResolver(); - - boolean isAddEvent = false; - - if (ACTION_ADD_SESSION_CALENDAR.equals(action)) { - isAddEvent = true; - - } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) { - isAddEvent = false; - - } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action) && - PrefUtils.shouldSyncCalendar(this)) { - try { - getContentResolver().applyBatch(CalendarContract.AUTHORITY, - processAllSessionsCalendar(resolver, getCalendarId(intent))); - sendBroadcast(new Intent( - SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED)); - } catch (RemoteException e) { - LOGE(TAG, "Error adding all sessions to Google Calendar", e); - } catch (OperationApplicationException e) { - LOGE(TAG, "Error adding all sessions to Google Calendar", e); - } - - } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) { - try { - getContentResolver().applyBatch(CalendarContract.AUTHORITY, - processClearAllSessions(resolver, getCalendarId(intent))); - } catch (RemoteException e) { - LOGE(TAG, "Error clearing all sessions from Google Calendar", e); - } catch (OperationApplicationException e) { - LOGE(TAG, "Error clearing all sessions from Google Calendar", e); - } - - } else { - return; - } - - final Uri uri = intent.getData(); - final Bundle extras = intent.getExtras(); - if (uri == null || extras == null || !PrefUtils.shouldSyncCalendar(this)) { - return; - } - - try { - resolver.applyBatch(CalendarContract.AUTHORITY, - processSessionCalendar(resolver, getCalendarId(intent), isAddEvent, uri, - extras.getLong(EXTRA_SESSION_START), - extras.getLong(EXTRA_SESSION_END), - extras.getString(EXTRA_SESSION_TITLE), - extras.getString(EXTRA_SESSION_ROOM))); - } catch (RemoteException e) { - LOGE(TAG, "Error adding session to Google Calendar", e); - } catch (OperationApplicationException e) { - LOGE(TAG, "Error adding session to Google Calendar", e); - } - } - - /** - * Gets the currently-logged in user's Google Calendar, or the Google Calendar for the user - * specified in the given intent's {@link #EXTRA_ACCOUNT_NAME}. - */ - private long getCalendarId(Intent intent) { - final String accountName; - if (intent != null && intent.hasExtra(EXTRA_ACCOUNT_NAME)) { - accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME); - } else { - accountName = AccountUtils.getActiveAccountName(this); - } - - if (TextUtils.isEmpty(accountName)) { - return INVALID_CALENDAR_ID; - } - - // TODO: The calendar ID should be stored in shared preferences upon choosing an account. - Cursor calendarsCursor = getContentResolver().query( - CalendarContract.Calendars.CONTENT_URI, - new String[]{"_id"}, - // TODO: What if the calendar is not displayed or not sync'd? - "account_name = ownerAccount and account_name = ?", - new String[]{accountName}, - null); - - long calendarId = INVALID_CALENDAR_ID; - if (calendarsCursor != null && calendarsCursor.moveToFirst()) { - calendarId = calendarsCursor.getLong(0); - calendarsCursor.close(); - } - - return calendarId; - } - - private String makeCalendarEventTitle(String sessionTitle) { - return sessionTitle + getResources().getString(R.string.session_calendar_suffix); - } - - /** - * Processes all sessions in the - * {@link com.google.samples.apps.iosched.provider.ScheduleProvider}, adding or removing - * calendar events to/from the specified Google Calendar depending on whether a session is - * in the user's schedule or not. - */ - private ArrayList processAllSessionsCalendar(ContentResolver resolver, - final long calendarId) { - - ArrayList batch = new ArrayList(); - - // Unable to find the Calendar associated with the user. Stop here. - if (calendarId == INVALID_CALENDAR_ID) { - return batch; - } - - // Retrieves all sessions. For each session, add to Calendar if starred and attempt to - // remove from Calendar if unstarred. - Cursor cursor = resolver.query( - ScheduleContract.Sessions.CONTENT_URI, - SessionsQuery.PROJECTION, - null, null, null); - - if (cursor != null) { - while (cursor.moveToNext()) { - Uri uri = ScheduleContract.Sessions.buildSessionUri( - Long.valueOf(cursor.getLong(0)).toString()); - boolean isAddEvent = (cursor.getInt(SessionsQuery.SESSION_IN_MY_SCHEDULE) == 1); - if (isAddEvent) { - batch.addAll(processSessionCalendar(resolver, - calendarId, isAddEvent, uri, - cursor.getLong(SessionsQuery.SESSION_START), - cursor.getLong(SessionsQuery.SESSION_END), - cursor.getString(SessionsQuery.SESSION_TITLE), - cursor.getString(SessionsQuery.ROOM_NAME))); - } - } - cursor.close(); - } - - return batch; - } - - /** - * Adds or removes a single session to/from the specified Google Calendar. - */ - private ArrayList processSessionCalendar( - final ContentResolver resolver, - final long calendarId, final boolean isAddEvent, - final Uri sessionUri, final long sessionBlockStart, final long sessionBlockEnd, - final String sessionTitle, final String sessionRoom) { - ArrayList batch = new ArrayList(); - - // Unable to find the Calendar associated with the user. Stop here. - if (calendarId == INVALID_CALENDAR_ID) { - return batch; - } - - final String calendarEventTitle = makeCalendarEventTitle(sessionTitle); - - Cursor cursor; - ContentValues values = new ContentValues(); - - // Add Calendar event. - if (isAddEvent) { - if (sessionBlockStart == 0L || sessionBlockEnd == 0L || sessionTitle == null) { - LOGW(TAG, "Unable to add a Calendar event due to insufficient input parameters."); - return batch; - } - - // Check if the calendar event exists first. If it does, we don't want to add a - // duplicate one. - cursor = resolver.query( - CalendarContract.Events.CONTENT_URI, // URI - new String[] {CalendarContract.Events._ID}, // Projection - CalendarContract.Events.CALENDAR_ID + "=? and " // Selection - + CalendarContract.Events.TITLE + "=? and " - + CalendarContract.Events.DTSTART + ">=? and " - + CalendarContract.Events.DTEND + "<=?", - new String[]{ // Selection args - Long.valueOf(calendarId).toString(), - calendarEventTitle, - Long.toString(Config.CONFERENCE_START_MILLIS), - Long.toString(Config.CONFERENCE_END_MILLIS) - }, - null); - - long newEventId = -1; - - if (cursor != null && cursor.moveToFirst()) { - // Calendar event already exists for this session. - newEventId = cursor.getLong(0); - cursor.close(); - - // Data fix (workaround): - batch.add( - ContentProviderOperation.newUpdate(CalendarContract.Events.CONTENT_URI) - .withValue(CalendarContract.Events.EVENT_TIMEZONE, - Config.CONFERENCE_TIMEZONE.getID()) - .withSelection(CalendarContract.Events._ID + "=?", - new String[]{Long.valueOf(newEventId).toString()}) - .build() - ); - // End data fix. - - } else { - // Calendar event doesn't exist, create it. - - // NOTE: we can't use batch processing here because we need the result of - // the insert. - values.clear(); - values.put(CalendarContract.Events.DTSTART, sessionBlockStart); - values.put(CalendarContract.Events.DTEND, sessionBlockEnd); - values.put(CalendarContract.Events.EVENT_LOCATION, sessionRoom); - values.put(CalendarContract.Events.TITLE, calendarEventTitle); - values.put(CalendarContract.Events.CALENDAR_ID, calendarId); - values.put(CalendarContract.Events.EVENT_TIMEZONE, - Config.CONFERENCE_TIMEZONE.getID()); - Uri eventUri = resolver.insert(CalendarContract.Events.CONTENT_URI, values); - String eventId = eventUri.getLastPathSegment(); - if (eventId == null) { - return batch; // Should be empty at this point - } - - newEventId = Long.valueOf(eventId); - // Since we're adding session reminder to system notification, we're not creating - // Calendar event reminders. If we were to create Calendar event reminders, this - // is how we would do it. - //values.put(CalendarContract.Reminders.EVENT_ID, Integer.valueOf(eventId)); - //values.put(CalendarContract.Reminders.MINUTES, 10); - //values.put(CalendarContract.Reminders.METHOD, - // CalendarContract.Reminders.METHOD_ALERT); // Or default? - //cr.insert(CalendarContract.Reminders.CONTENT_URI, values); - //values.clear(); - } - - // Update the session in our own provider with the newly created calendar event ID. - values.clear(); - values.put(ScheduleContract.Sessions.SESSION_CAL_EVENT_ID, newEventId); - resolver.update(sessionUri, values, null, null); - - } else { - // Remove Calendar event, if exists. - - // Get the event calendar id. - cursor = resolver.query(sessionUri, - new String[] {ScheduleContract.Sessions.SESSION_CAL_EVENT_ID}, - null, null, null); - long calendarEventId = -1; - if (cursor != null && cursor.moveToFirst()) { - calendarEventId = cursor.getLong(0); - cursor.close(); - } - - // Try to remove the Calendar Event based on key. If successful, move on; - // otherwise, remove the event based on Event title. - int affectedRows = 0; - if (calendarEventId != -1) { - affectedRows = resolver.delete( - CalendarContract.Events.CONTENT_URI, - CalendarContract.Events._ID + "=?", - new String[]{Long.valueOf(calendarEventId).toString()}); - } - - if (affectedRows == 0) { - resolver.delete(CalendarContract.Events.CONTENT_URI, - String.format("%s=? and %s=? and %s=? and %s=?", - CalendarContract.Events.CALENDAR_ID, - CalendarContract.Events.TITLE, - CalendarContract.Events.DTSTART, - CalendarContract.Events.DTEND), - new String[]{Long.valueOf(calendarId).toString(), - calendarEventTitle, - Long.valueOf(sessionBlockStart).toString(), - Long.valueOf(sessionBlockEnd).toString()}); - } - - // Remove the session and calendar event association. - values.clear(); - values.put(ScheduleContract.Sessions.SESSION_CAL_EVENT_ID, (Long) null); - resolver.update(sessionUri, values, null, null); - } - - return batch; - } - - /** - * Removes all calendar entries associated with Google I/O 2013. - */ - private ArrayList processClearAllSessions( - ContentResolver resolver, long calendarId) { - - ArrayList batch = new ArrayList(); - - // Unable to find the Calendar associated with the user. Stop here. - if (calendarId == INVALID_CALENDAR_ID) { - Log.e(TAG, "Unable to find Calendar for user"); - return batch; - } - - // Delete all calendar entries matching the given title within the given time period - batch.add(ContentProviderOperation - .newDelete(CalendarContract.Events.CONTENT_URI) - .withSelection( - CalendarContract.Events.CALENDAR_ID + " = ? and " - + CalendarContract.Events.TITLE + " LIKE ? and " - + CalendarContract.Events.DTSTART + ">= ? and " - + CalendarContract.Events.DTEND + "<= ?", - new String[]{ - Long.toString(calendarId), - CALENDAR_CLEAR_SEARCH_LIKE_EXPRESSION, - Long.toString(Config.CONFERENCE_START_MILLIS), - Long.toString(Config.CONFERENCE_END_MILLIS) - } - ) - .build()); - - return batch; - } - - private interface SessionsQuery { - String[] PROJECTION = { - ScheduleContract.Sessions._ID, - ScheduleContract.Sessions.SESSION_START, - ScheduleContract.Sessions.SESSION_END, - ScheduleContract.Sessions.SESSION_TITLE, - ScheduleContract.Sessions.ROOM_NAME, - ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE, - }; - - int _ID = 0; - int SESSION_START = 1; - int SESSION_END = 2; - int SESSION_TITLE = 3; - int ROOM_NAME = 4; - int SESSION_IN_MY_SCHEDULE = 5; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java b/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java deleted file mode 100644 index 7ac3f54786..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java +++ /dev/null @@ -1,328 +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 com.google.samples.apps.iosched.sync; - -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.Context; -import android.content.OperationApplicationException; -import android.net.Uri; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.io.*; -import com.google.samples.apps.iosched.io.map.model.Tile; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.util.FileUtils; -import com.google.samples.apps.iosched.util.Lists; -import com.google.samples.apps.iosched.util.MapUtils; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; - -import java.io.*; -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; - -import com.larvalabs.svgandroid.SVG; -import com.larvalabs.svgandroid.SVGBuilder; -import com.larvalabs.svgandroid.SVGParseException; -import com.turbomanage.httpclient.BasicHttpClient; -import com.turbomanage.httpclient.ConsoleRequestLogger; -import com.turbomanage.httpclient.HttpResponse; -import com.turbomanage.httpclient.RequestLogger; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Helper class that parses conference data and imports them into the app's - * Content Provider. - */ -public class ConferenceDataHandler { - private static final String TAG = makeLogTag(SyncHelper.class); - - // Shared preferences key under which we store the timestamp that corresponds to - // the data we currently have in our content provider. - private static final String SP_KEY_DATA_TIMESTAMP = "data_timestamp"; - - // symbolic timestamp to use when we are missing timestamp data (which means our data is - // really old or nonexistent) - private static final String DEFAULT_TIMESTAMP = "Sat, 1 Jan 2000 00:00:00 GMT"; - - private static final String DATA_KEY_ROOMS = "rooms"; - private static final String DATA_KEY_BLOCKS = "blocks"; - private static final String DATA_KEY_TAGS = "tags"; - private static final String DATA_KEY_SPEAKERS = "speakers"; - private static final String DATA_KEY_SESSIONS = "sessions"; - private static final String DATA_KEY_SEARCH_SUGGESTIONS = "search_suggestions"; - private static final String DATA_KEY_MAP = "map"; - private static final String DATA_KEY_HASHTAGS = "hashtags"; - private static final String DATA_KEY_EXPERTS = "experts"; - private static final String DATA_KEY_VIDEOS = "video_library"; - private static final String DATA_KEY_PARTNERS = "partners"; - - private static final String[] DATA_KEYS_IN_ORDER = { - DATA_KEY_ROOMS, - DATA_KEY_BLOCKS, - DATA_KEY_TAGS, - DATA_KEY_SPEAKERS, - DATA_KEY_SESSIONS, - DATA_KEY_SEARCH_SUGGESTIONS, - DATA_KEY_MAP, - DATA_KEY_HASHTAGS, - DATA_KEY_EXPERTS, - DATA_KEY_VIDEOS, - DATA_KEY_PARTNERS - }; - - Context mContext = null; - - // Handlers for each entity type: - RoomsHandler mRoomsHandler = null; - BlocksHandler mBlocksHandler = null; - TagsHandler mTagsHandler = null; - SpeakersHandler mSpeakersHandler = null; - SessionsHandler mSessionsHandler = null; - SearchSuggestHandler mSearchSuggestHandler = null; - MapPropertyHandler mMapPropertyHandler = null; - ExpertsHandler mExpertsHandler = null; - HashtagsHandler mHashtagsHandler = null; - VideosHandler mVideosHandler = null; - PartnersHandler mPartnersHandler = null; - - // Convenience map that maps the key name to its corresponding handler (e.g. - // "blocks" to mBlocksHandler (to avoid very tedious if-elses) - HashMap mHandlerForKey = new HashMap(); - - // Tally of total content provider operations we carried out (for statistical purposes) - private int mContentProviderOperationsDone = 0; - - public ConferenceDataHandler(Context ctx) { - mContext = ctx; - } - - /** - * Parses the conference data in the given objects and imports the data into the - * content provider. The format of the data is documented at https://code.google.com/p/iosched. - * - * @param dataBodies The collection of JSON objects to parse and import. - * @param dataTimestamp The timestamp of the data. This should be in RFC1123 format. - * @param downloadsAllowed Whether or not we are supposed to download data from the internet if needed. - * @throws IOException If there is a problem parsing the data. - */ - public void applyConferenceData(String[] dataBodies, String dataTimestamp, - boolean downloadsAllowed) throws IOException { - LOGD(TAG, "Applying data from " + dataBodies.length + " files, timestamp " + dataTimestamp); - - // create handlers for each data type - mHandlerForKey.put(DATA_KEY_ROOMS, mRoomsHandler = new RoomsHandler(mContext)); - mHandlerForKey.put(DATA_KEY_BLOCKS, mBlocksHandler = new BlocksHandler(mContext)); - mHandlerForKey.put(DATA_KEY_TAGS, mTagsHandler = new TagsHandler(mContext)); - mHandlerForKey.put(DATA_KEY_SPEAKERS, mSpeakersHandler = new SpeakersHandler(mContext)); - mHandlerForKey.put(DATA_KEY_SESSIONS, mSessionsHandler = new SessionsHandler(mContext)); - mHandlerForKey.put(DATA_KEY_SEARCH_SUGGESTIONS, mSearchSuggestHandler = - new SearchSuggestHandler(mContext)); - mHandlerForKey.put(DATA_KEY_MAP, mMapPropertyHandler = new MapPropertyHandler(mContext)); - mHandlerForKey.put(DATA_KEY_EXPERTS, mExpertsHandler = new ExpertsHandler(mContext)); - mHandlerForKey.put(DATA_KEY_HASHTAGS, mHashtagsHandler = new HashtagsHandler(mContext)); - mHandlerForKey.put(DATA_KEY_VIDEOS, mVideosHandler = new VideosHandler(mContext)); - mHandlerForKey.put(DATA_KEY_PARTNERS, mPartnersHandler = new PartnersHandler(mContext)); - - // process the jsons. This will call each of the handlers when appropriate to deal - // with the objects we see in the data. - LOGD(TAG, "Processing " + dataBodies.length + " JSON objects."); - for (int i = 0; i < dataBodies.length; i++) { - LOGD(TAG, "Processing json object #" + (i + 1) + " of " + dataBodies.length); - processDataBody(dataBodies[i]); - } - - // the sessions handler needs to know the tag and speaker maps to process sessions - mSessionsHandler.setTagMap(mTagsHandler.getTagMap()); - mSessionsHandler.setSpeakerMap(mSpeakersHandler.getSpeakerMap()); - - // produce the necessary content provider operations - ArrayList batch = new ArrayList(); - for (String key : DATA_KEYS_IN_ORDER) { - LOGD(TAG, "Building content provider operations for: " + key); - mHandlerForKey.get(key).makeContentProviderOperations(batch); - LOGD(TAG, "Content provider operations so far: " + batch.size()); - } - LOGD(TAG, "Total content provider operations: " + batch.size()); - - // download or process local map tile overlay files (SVG files) - LOGD(TAG, "Processing map overlay files"); - processMapOverlayFiles(mMapPropertyHandler.getTileOverlays(), downloadsAllowed); - - // finally, push the changes into the Content Provider - LOGD(TAG, "Applying " + batch.size() + " content provider operations."); - try { - int operations = batch.size(); - if (operations > 0) { - mContext.getContentResolver().applyBatch(ScheduleContract.CONTENT_AUTHORITY, batch); - } - LOGD(TAG, "Successfully applied " + operations + " content provider operations."); - mContentProviderOperationsDone += operations; - } catch (RemoteException ex) { - LOGE(TAG, "RemoteException while applying content provider operations."); - throw new RuntimeException("Error executing content provider batch operation", ex); - } catch (OperationApplicationException ex) { - LOGE(TAG, "OperationApplicationException while applying content provider operations."); - throw new RuntimeException("Error executing content provider batch operation", ex); - } - - // notify all top-level paths - LOGD(TAG, "Notifying changes on all top-level paths on Content Resolver."); - ContentResolver resolver = mContext.getContentResolver(); - for (String path : ScheduleContract.TOP_LEVEL_PATHS) { - Uri uri = ScheduleContract.BASE_CONTENT_URI.buildUpon().appendPath(path).build(); - resolver.notifyChange(uri, null); - } - - - // update our data timestamp - setDataTimestamp(dataTimestamp); - LOGD(TAG, "Done applying conference data."); - } - - public int getContentProviderOperationsDone() { - return mContentProviderOperationsDone; - } - - /** - * Processes a conference data body and calls the appropriate data type handlers - * to process each of the objects represented therein. - * - * @param dataBody The body of data to process - * @throws IOException If there is an error parsing the data. - */ - private void processDataBody(String dataBody) throws IOException { - JsonReader reader = new JsonReader(new StringReader(dataBody)); - JsonParser parser = new JsonParser(); - try { - reader.setLenient(true); // To err is human - - // the whole file is a single JSON object - reader.beginObject(); - - while (reader.hasNext()) { - // the key is "rooms", "speakers", "tracks", etc. - String key = reader.nextName(); - if (mHandlerForKey.containsKey(key)) { - // pass the value to the corresponding handler - mHandlerForKey.get(key).process(parser.parse(reader)); - } else { - LOGW(TAG, "Skipping unknown key in conference data json: " + key); - reader.skipValue(); - } - } - reader.endObject(); - } finally { - reader.close(); - } - } - - /** - * Synchronise the map overlay files either from the local assets (if available) or from a remote url. - * - * @param collection Set of tiles containing a local filename and remote url. - * @throws IOException - */ - private void processMapOverlayFiles(Collection collection, boolean downloadAllowed) throws IOException, SVGParseException { - // clear the tile cache on disk if any tiles have been updated - boolean shouldClearCache = false; - // keep track of used files, unused files are removed - ArrayList usedTiles = Lists.newArrayList(); - for (Tile tile : collection) { - final String filename = tile.filename; - final String url = tile.url; - - usedTiles.add(filename); - - if (!MapUtils.hasTile(mContext, filename)) { - shouldClearCache = true; - // copy or download the tile if it is not stored yet - if (MapUtils.hasTileAsset(mContext, filename)) { - // file already exists as an asset, copy it - MapUtils.copyTileAsset(mContext, filename); - } else if (downloadAllowed && !TextUtils.isEmpty(url)) { - try { - // download the file only if downloads are allowed and url is not empty - File tileFile = MapUtils.getTileFile(mContext, filename); - BasicHttpClient httpClient = new BasicHttpClient(); - httpClient.setRequestLogger(mQuietLogger); - HttpResponse httpResponse = httpClient.get(url, null); - FileUtils.writeFile(httpResponse.getBody(), tileFile); - - // ensure the file is valid SVG - InputStream is = new FileInputStream(tileFile); - SVG svg = new SVGBuilder().readFromInputStream(is).build(); - is.close(); - } catch (IOException ex) { - LOGE(TAG, "FAILED downloading map overlay tile "+url+ - ": " + ex.getMessage(), ex); - } catch (SVGParseException ex) { - LOGE(TAG, "FAILED parsing map overlay tile "+url+ - ": " + ex.getMessage(), ex); - } - } else { - LOGD(TAG, "Skipping download of map overlay tile" + - " (since downloadsAllowed=false)"); - } - } - } - - if (shouldClearCache) { - MapUtils.clearDiskCache(mContext); - } - - MapUtils.removeUnusedTiles(mContext, usedTiles); - } - - // Returns the timestamp of the data we have in the content provider. - public String getDataTimestamp() { - return PreferenceManager.getDefaultSharedPreferences(mContext).getString( - SP_KEY_DATA_TIMESTAMP, DEFAULT_TIMESTAMP); - } - - // Sets the timestamp of the data we have in the content provider. - public void setDataTimestamp(String timestamp) { - LOGD(TAG, "Setting data timestamp to: " + timestamp); - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString( - SP_KEY_DATA_TIMESTAMP, timestamp).commit(); - } - - // Reset the timestamp of the data we have in the content provider - public static void resetDataTimestamp(final Context context) { - LOGD(TAG, "Resetting data timestamp to default (to invalidate our synced data)"); - PreferenceManager.getDefaultSharedPreferences(context).edit().remove( - SP_KEY_DATA_TIMESTAMP).commit(); - } - - /** - * A type of ConsoleRequestLogger that does not log requests and responses. - */ - private RequestLogger mQuietLogger = new ConsoleRequestLogger(){ - @Override - public void logRequest(HttpURLConnection uc, Object content) throws IOException { } - - @Override - public void logResponse(HttpResponse res) { } - }; - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java b/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java deleted file mode 100644 index 965010329e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java +++ /dev/null @@ -1,86 +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 com.google.samples.apps.iosched.sync; - -import android.content.Context; - -import com.google.samples.apps.iosched.Config; - -import java.net.HttpURLConnection; -import java.util.List; - -import com.turbomanage.httpclient.BasicHttpClient; -import com.turbomanage.httpclient.HttpResponse; -import com.turbomanage.httpclient.ParameterMap; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Created by lwray on 5/7/14. - */ -public class EventFeedbackApi { - private static final String TAG = makeLogTag(EventFeedbackApi.class); - - - private static final String PARAMETER_EVENT_CODE = "code"; - private static final String PARAMETER_API_KEY = "apikey"; - - private static final String PARAMETER_SESSION_ID = "objectid"; - private static final String PARAMETER_SURVEY_ID = "surveyId"; - private static final String PARAMETER_REGISTRANT_ID = "registrantKey"; - private final Context mContext; - private final String mUrl; - - public EventFeedbackApi(Context context) { - mContext = context; - mUrl = Config.FEEDBACK_URL; - } - - /** - * Posts a session to the event server. - * - * @param sessionId The ID of the session that was reviewed. - * @return whether or not updating succeeded - */ - public boolean sendSessionToServer(String sessionId, List questions) { - - BasicHttpClient httpClient = new BasicHttpClient(); - httpClient.addHeader(PARAMETER_EVENT_CODE, Config.FEEDBACK_API_CODE); - httpClient.addHeader(PARAMETER_API_KEY, Config.FEEDBACK_API_KEY); - - ParameterMap parameterMap = httpClient.newParams(); - parameterMap.add(PARAMETER_SESSION_ID, sessionId); - parameterMap.add(PARAMETER_SURVEY_ID, Config.FEEDBACK_SURVEY_ID); - parameterMap.add(PARAMETER_REGISTRANT_ID, Config.FEEDBACK_DUMMY_REGISTRANT_ID); - int i = 1; - for (String question : questions) { - parameterMap.add("q" + i, question); - i++; - } - - HttpResponse response = httpClient.get(mUrl, parameterMap); - - if (response != null && response.getStatus() == HttpURLConnection.HTTP_OK) { - LOGD(TAG, "Server returned HTTP_OK, so session posting was successful."); - return true; - } else { - LOGE(TAG, "Error posting session: HTTP status " + response.getStatus()); - return false; - } - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java deleted file mode 100644 index 77df3da2e2..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java +++ /dev/null @@ -1,88 +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 com.google.samples.apps.iosched.sync; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; - -import com.google.samples.apps.iosched.provider.ScheduleContract; - -import java.util.ArrayList; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Created by lwray on 5/8/14. - */ -public class FeedbackSyncHelper { - private static final String TAG = makeLogTag(FeedbackSyncHelper.class); - - - Context mContext; - EventFeedbackApi mEventFeedbackApi; - - FeedbackSyncHelper(Context context) { - mContext = context; - mEventFeedbackApi = new EventFeedbackApi(context); - - } - - public void sync() { - final ContentResolver cr = mContext.getContentResolver(); - final Uri newFeedbackUri = ScheduleContract.Feedback.CONTENT_URI; - Cursor c = cr.query(newFeedbackUri, - null, - ScheduleContract.Feedback.SYNCED + " = 0", - null, - null); - LOGD(TAG, "Number of unsynced feedbacks: " + c.getCount()); - List questions = new ArrayList(); - List updatedSessions = new ArrayList(); - - while (c.moveToNext()) { - String sessionId = c.getString(c.getColumnIndex(ScheduleContract.Feedback.SESSION_ID)); - - questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.SESSION_RATING))); - questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_RELEVANCE))); - questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_CONTENT))); - questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_SPEAKER))); - questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.COMMENTS))); - - if (mEventFeedbackApi.sendSessionToServer(sessionId, questions)) { - LOGI(TAG, "Successfully updated session " + sessionId); - updatedSessions.add(sessionId); - } else { - LOGE(TAG, "Couldn't update session " + sessionId); - } - } - - c.close(); - - // Flip the "synced" flag to true for any successfully updated sessions, but leave them - // in the database to prevent duplicate feedback - ContentValues contentValues = new ContentValues(); - contentValues.put(ScheduleContract.Feedback.SYNCED, 1); - for (String sessionId : updatedSessions) { - cr.update(ScheduleContract.Feedback.buildFeedbackUri(sessionId), contentValues, null, null); - } - - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java b/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java deleted file mode 100644 index 38f4c02c37..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java +++ /dev/null @@ -1,402 +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 com.google.samples.apps.iosched.sync; - -import android.content.Context; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.Config; -import com.google.gson.Gson; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.io.model.DataManifest; -import com.google.samples.apps.iosched.util.FileUtils; -import com.google.samples.apps.iosched.util.HashUtils; -import com.google.samples.apps.iosched.util.TimeUtils; - -import java.io.File; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.HashSet; -import java.util.List; - -import com.turbomanage.httpclient.BasicHttpClient; -import com.turbomanage.httpclient.ConsoleRequestLogger; -import com.turbomanage.httpclient.HttpResponse; -import com.turbomanage.httpclient.RequestLogger; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Helper class that fetches conference data from the remote server. - */ -public class RemoteConferenceDataFetcher { - private static final String TAG = makeLogTag(SyncHelper.class); - - // The directory under which we cache our downloaded files - private static String CACHE_DIR = "data_cache"; - - private Context mContext = null; - - // name of URL override file used for debug purposes - private static final String URL_OVERRIDE_FILE_NAME = "iosched_manifest_url_override.txt"; - - // URL of the remote manifest file - private String mManifestUrl = null; - - // timestamp of the manifest file on the server - private String mServerTimestamp = null; - - // the set of cache files we have used -- we use this for cache cleanup. - private HashSet mCacheFilesToKeep = new HashSet(); - - // total # of bytes downloaded (approximate) - private long mBytesDownloaded = 0; - - // total # of bytes read from cache hits (approximate) - private long mBytesReadFromCache = 0; - - public RemoteConferenceDataFetcher(Context context) { - mContext = context; - mManifestUrl = getManifestUrl(); - } - - /** - * Fetches data from the remote server. - * - * @param refTimestamp The timestamp of the data to use as a reference; if the remote data - * is not newer than this timestamp, no data will be downloaded and - * this method will return null. - * - * @return The data downloaded, or null if there is no data to download - * @throws IOException if an error occurred during download. - */ - public String[] fetchConferenceDataIfNewer(String refTimestamp) throws IOException { - if (TextUtils.isEmpty(mManifestUrl)) { - LOGW(TAG, "Manifest URL is empty (remote sync disabled!)."); - return null; - } - - BasicHttpClient httpClient = new BasicHttpClient(); - httpClient.setRequestLogger(mQuietLogger); - - // Only download if data is newer than refTimestamp - // Cloud Storage is very picky with the If-Modified-Since format. If it's in a wrong - // format, it refuses to serve the file, returning 400 HTTP error. So, if the - // refTimestamp is in a wrong format, we simply ignore it. But pay attention to this - // warning in the log, because it might mean unnecessary data is being downloaded. - if (!TextUtils.isEmpty(refTimestamp)) { - if (TimeUtils.isValidFormatForIfModifiedSinceHeader(refTimestamp)) { - httpClient.addHeader("If-Modified-Since", refTimestamp); - } else { - LOGW(TAG, "Could not set If-Modified-Since HTTP header. Potentially downloading " + - "unnecessary data. Invalid format of refTimestamp argument: "+refTimestamp); - } - } - - HttpResponse response = httpClient.get(mManifestUrl, null); - if (response == null) { - LOGE(TAG, "Request for manifest returned null response."); - throw new IOException("Request for data manifest returned null response."); - } - - int status = response.getStatus(); - if (status == HttpURLConnection.HTTP_OK) { - LOGD(TAG, "Server returned HTTP_OK, so new data is available."); - mServerTimestamp = getLastModified(response); - LOGD(TAG, "Server timestamp for new data is: " + mServerTimestamp); - String body = response.getBodyAsString(); - if (TextUtils.isEmpty(body)) { - LOGE(TAG, "Request for manifest returned empty data."); - throw new IOException("Error fetching conference data manifest: no data."); - } - LOGD(TAG, "Manifest "+mManifestUrl+" read, contents: " + body); - mBytesDownloaded += body.getBytes().length; - return processManifest(body); - } else if (status == HttpURLConnection.HTTP_NOT_MODIFIED) { - // data on the server is not newer than our data - LOGD(TAG, "HTTP_NOT_MODIFIED: data has not changed since " + refTimestamp); - return null; - } else { - LOGE(TAG, "Error fetching conference data: HTTP status " + status); - throw new IOException("Error fetching conference data: HTTP status " + status); - } - } - - // Returns the timestamp of the data downloaded from the server - public String getServerDataTimestamp() { - return mServerTimestamp; - } - - /** - * Returns the remote manifest file's URL. This is stored as a resource in the app, - * but can be overriden by a file in the filesystem for debug purposes. - * @return The URL of the remote manifest file. - */ - private String getManifestUrl() { - - String manifestUrl = Config.MANIFEST_URL; - - // check for an override file - File urlOverrideFile = new File(mContext.getFilesDir(), URL_OVERRIDE_FILE_NAME); - if (urlOverrideFile.exists()) { - try { - String overrideUrl = FileUtils.readFileAsString(urlOverrideFile).trim(); - LOGW(TAG, "Debug URL override active: " + overrideUrl); - return overrideUrl; - } catch (IOException ex) { - return manifestUrl; - } - } else { - return manifestUrl; - } - } - - /** - * Fetches a file from the cache/network, from an absolute or relative URL. If the - * file is available in our cache, we read it from there; if not, we will - * download it from the network and cache it. - * - * @param url The URL to fetch the file from. The URL may be absolute or relative; if - * relative, it will be considered to be relative to the manifest URL. - * @return The contents of the file. - * @throws IOException If an error occurs. - */ - private String fetchFile(String url) throws IOException { - // If this is a relative url, consider it relative to the manifest URL - if (!url.contains("://")) { - if (TextUtils.isEmpty(mManifestUrl) || !mManifestUrl.contains("/")) { - LOGE(TAG, "Could not build relative URL based on manifest URL."); - return null; - } - int i = mManifestUrl.lastIndexOf('/'); - url = mManifestUrl.substring(0, i) + "/" + url; - } - - LOGD(TAG, "Attempting to fetch: " + sanitizeUrl(url)); - - // Check if we have it in our cache first - String body = null; - try { - body = loadFromCache(url); - if (!TextUtils.isEmpty(body)) { - // cache hit - mBytesReadFromCache += body.getBytes().length; - mCacheFilesToKeep.add(getCacheKey(url)); - return body; - } - } catch (IOException ex) { - ex.printStackTrace(); - LOGE(TAG, "IOException getting file from cache."); - // proceed anyway to attempt to download it from the network - } - - // We don't have the file on cache, so download it - LOGD(TAG, "Cache miss. Downloading from network: " + sanitizeUrl(url)); - BasicHttpClient client = new BasicHttpClient(); - client.setRequestLogger(mQuietLogger); - HttpResponse response = client.get(url, null); - - if (response == null) { - throw new IOException("Request for URL " + sanitizeUrl(url) + " returned null response."); - } - - LOGD(TAG, "HTTP response " + response.getStatus()); - if (response.getStatus() == HttpURLConnection.HTTP_OK) { - body = response.getBodyAsString(); - if (TextUtils.isEmpty(body)) { - throw new IOException("Got empty response when attempting to fetch " + - sanitizeUrl(url)); - } - LOGD(TAG, "Successfully downloaded from network: " + sanitizeUrl(url)); - mBytesDownloaded += body.getBytes().length; - writeToCache(url, body); - mCacheFilesToKeep.add(getCacheKey(url)); - return body; - } else { - LOGE(TAG, "Failed to fetch from network: " + sanitizeUrl(url)); - throw new IOException("Request for URL " + sanitizeUrl(url) + - " failed with HTTP error " + response.getStatus()); - } - } - - /** - * Returns the cache file where we store our cache of the response of the given URL. - * @param url The URL for which to return the cache file. - * @return The cache file. - */ - private File getCacheFile(String url) { - String cacheKey = getCacheKey(url); - return new File(mContext.getCacheDir() + File.separator + CACHE_DIR + File.separator + - cacheKey); - } - - // Creates the cache directory, if it doesn't exist yet - private void createCacheDir() throws IOException { - File dir = new File(mContext.getCacheDir() + File.separator + CACHE_DIR); - if (!dir.exists() && !dir.mkdir()) { - throw new IOException("Failed to mkdir: " + dir); - } - } - - - /** - * Loads our cached content corresponding to the given URL. - * @param url The URL for which to load the cached response. - * @return The cached response corresponding to the URL; or null if the given URL - * does not exist in our cache. - * @throws IOException If there is an error reading the cache. - */ - private String loadFromCache(String url) throws IOException { - String cacheKey = getCacheKey(url); - File cacheFile = getCacheFile(url); - if (cacheFile.exists()) { - LOGD(TAG, "Cache hit " + cacheKey + " for " + sanitizeUrl(url)); - return FileUtils.readFileAsString(cacheFile); - } else { - LOGD(TAG, "Cache miss " + cacheKey + " for " + sanitizeUrl(url)); - return null; - } - } - - /** - * Writes a file to the cache. - * @param url The URL from which the contents were retrieved. - * @param body The contents retrieved from the given URL. - * @throws IOException If there is a problem writing the file. - */ - private void writeToCache(String url, String body) throws IOException { - String cacheKey = getCacheKey(url); - File cacheFile = getCacheFile(url); - createCacheDir(); - FileUtils.writeFile(body, cacheFile); - LOGD(TAG, "Wrote to cache " + cacheKey + " --> " + sanitizeUrl(url)); - } - - /** - * Returns the cache key to be used to store the given URL. The cache key is the - * file name under which the contents of the URL are stored. - * @param url The URL. - * @return The cache key (guaranteed to be a valid filename) - */ - private String getCacheKey(String url) { - return HashUtils.computeWeakHash(url.trim()) + String.format("%04x", url.length()); - } - - // Sanitize a URL for logging purposes (only the last component is left visible). - private String sanitizeUrl(String url) { - int i = url.lastIndexOf('/'); - if (i >= 0 && i < url.length()) { - return url.substring(0, i).replaceAll("[A-za-z]", "*") + - url.substring(i); - } - else return url.replaceAll("[A-za-z]", "*"); - } - - private static final String MANIFEST_FORMAT = "iosched-json-v1"; - - /** - * Process the data manifest and download data files referenced from it. - * @param manifestJson The JSON of the manifest file. - * @return The contents of the set of files referenced from the manifest, or null - * if none could be retrieved. - * @throws IOException If an error occurs while retrieving information. - */ - private String[] processManifest(String manifestJson) throws IOException { - LOGD(TAG, "Processing data manifest, length " + manifestJson.length()); - - DataManifest manifest = new Gson().fromJson(manifestJson, DataManifest.class); - if (manifest.format == null || !manifest.format.equals(MANIFEST_FORMAT)) { - LOGE(TAG, "Manifest has invalid format spec: " + manifest.format); - throw new IOException("Invalid format spec on manifest:" + manifest.format); - } - - if (manifest.data_files == null || manifest.data_files.length == 0) { - LOGW(TAG, "Manifest does not list any files. Nothing done."); - return null; - } - - LOGD(TAG, "Manifest lists " + manifest.data_files.length + " data files."); - String[] jsons = new String[manifest.data_files.length]; - for (int i = 0; i < manifest.data_files.length; i++) { - String url = manifest.data_files[i]; - LOGD(TAG, "Processing data file: " + sanitizeUrl(url)); - jsons[i] = fetchFile(url); - if (TextUtils.isEmpty(jsons[i])) { - LOGE(TAG, "Failed to fetch data file: " + sanitizeUrl(url)); - throw new IOException("Failed to fetch data file " + sanitizeUrl(url)); - } - } - - LOGD(TAG, "Got " + jsons.length + " data files."); - cleanUpCache(); - return jsons; - } - - // Delete unnecessary files from our cache - private void cleanUpCache() { - LOGD(TAG, "Starting cache cleanup, " + mCacheFilesToKeep.size() + " URLs to keep."); - File dir = new File(mContext.getCacheDir() + File.separator + CACHE_DIR); - if (!dir.exists()) { - LOGD(TAG, "Cleanup complete (there is no cache)."); - return; - } - - int deleted = 0, kept = 0; - for (File file : dir.listFiles()) { - if (mCacheFilesToKeep.contains(file.getName())) { - LOGD(TAG, "Cache cleanup: KEEEPING " + file.getName()); - ++kept; - } else { - LOGD(TAG, "Cache cleanup: DELETING " + file.getName()); - file.delete(); - ++deleted; - } - } - - LOGD(TAG, "End of cache cleanup. " + kept + " files kept, " + deleted + " deleted."); - } - - public long getTotalBytesDownloaded() { - return mBytesDownloaded; - } - - public long getTotalBytesReadFromCache() { - return mBytesReadFromCache; - } - - private String getLastModified(HttpResponse resp) { - if (!resp.getHeaders().containsKey("Last-Modified")) { - return ""; - } - - List s = resp.getHeaders().get("Last-Modified"); - return s.isEmpty() ? "" : s.get(0); - } - - /** - * A type of ConsoleRequestLogger that does not log requests and responses. - */ - private RequestLogger mQuietLogger = new ConsoleRequestLogger(){ - @Override - public void logRequest(HttpURLConnection uc, Object content) throws IOException { } - - @Override - public void logResponse(HttpResponse res) { } - }; - - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.java deleted file mode 100755 index 9c16ee6041..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.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. - */ - -package com.google.samples.apps.iosched.sync; - -import android.accounts.Account; -import android.content.*; -import android.os.Bundle; - -import com.google.samples.apps.iosched.BuildConfig; - -import java.util.regex.Pattern; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * Sync adapter for Google I/O data - */ -public class SyncAdapter extends AbstractThreadedSyncAdapter { - private static final String TAG = makeLogTag(SyncAdapter.class); - - private static final Pattern sSanitizeAccountNamePattern = Pattern.compile("(.).*?(.?)@"); - public static final String EXTRA_SYNC_USER_DATA_ONLY = "com.google.samples.apps.iosched.EXTRA_SYNC_USER_DATA_ONLY";; - - private final Context mContext; - - public SyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - mContext = context; - - //noinspection ConstantConditions,PointlessBooleanExpression - if (!BuildConfig.DEBUG) { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - LOGE(TAG, "Uncaught sync exception, suppressing UI in release build.", - throwable); - } - }); - } - } - - @Override - public void onPerformSync(final Account account, Bundle extras, String authority, - final ContentProviderClient provider, final SyncResult syncResult) { - final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); - final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - final boolean initialize = extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); - final boolean userDataOnly = extras.getBoolean(EXTRA_SYNC_USER_DATA_ONLY, false); - - final String logSanitizedAccountName = sSanitizeAccountNamePattern - .matcher(account.name).replaceAll("$1...$2@"); - - if (uploadOnly) { - return; - } - - LOGI(TAG, "Beginning sync for account " + logSanitizedAccountName + "," + - " uploadOnly=" + uploadOnly + - " manualSync=" + manualSync + - " userDataOnly =" + userDataOnly + - " initialize=" + initialize); - - // Sync from bootstrap and remote data, as needed - new SyncHelper(mContext).performSync(syncResult, account, extras); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java deleted file mode 100644 index 380e2226eb..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java +++ /dev/null @@ -1,372 +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 com.google.samples.apps.iosched.sync; - -import android.accounts.Account; -import android.content.*; -import android.net.ConnectivityManager; -import android.os.Bundle; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.service.SessionAlarmService; -import com.google.samples.apps.iosched.service.SessionCalendarService; -import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper; -import com.google.samples.apps.iosched.sync.userdata.UserDataSyncHelperFactory; -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.UIUtils; - -import java.io.IOException; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * A helper class for dealing with conference data synchronization. - * All operations occur on the thread they're called from, so it's best to wrap - * calls in an {@link android.os.AsyncTask}, or better yet, a - * {@link android.app.Service}. - */ -public class SyncHelper { - private static final String TAG = makeLogTag("SyncHelper"); - - private Context mContext; - private ConferenceDataHandler mConferenceDataHandler; - private RemoteConferenceDataFetcher mRemoteDataFetcher; - - public SyncHelper(Context context) { - mContext = context; - mConferenceDataHandler = new ConferenceDataHandler(mContext); - mRemoteDataFetcher = new RemoteConferenceDataFetcher(mContext); - } - - public static void requestManualSync(Account mChosenAccount) { - requestManualSync(mChosenAccount, false); - } - public static void requestManualSync(Account mChosenAccount, boolean userDataSyncOnly) { - if (mChosenAccount != null) { - LOGD(TAG, "Requesting manual sync for account " + mChosenAccount.name - +" userDataSyncOnly="+userDataSyncOnly); - Bundle b = new Bundle(); - b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); - if (userDataSyncOnly) { - b.putBoolean(SyncAdapter.EXTRA_SYNC_USER_DATA_ONLY, true); - } - ContentResolver.setSyncAutomatically(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, true); - ContentResolver.setIsSyncable(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, 1); - - boolean pending = ContentResolver.isSyncPending(mChosenAccount, - ScheduleContract.CONTENT_AUTHORITY); - if (pending) { - LOGD(TAG, "Warning: sync is PENDING. Will cancel."); - } - boolean active = ContentResolver.isSyncActive(mChosenAccount, - ScheduleContract.CONTENT_AUTHORITY); - if (active) { - LOGD(TAG, "Warning: sync is ACTIVE. Will cancel."); - } - - if (pending || active) { - LOGD(TAG, "Cancelling previously pending/active sync."); - ContentResolver.cancelSync(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY); - } - - LOGD(TAG, "Requesting sync now."); - ContentResolver.requestSync(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, b); - } else { - LOGD(TAG, "Can't request manual sync -- no chosen account."); - } - } - - /** - * Attempts to perform conference data synchronization. The data comes from the remote URL - * configured in {@link com.google.samples.apps.iosched.Config#MANIFEST_URL}. The remote URL - * must point to a manifest file that, in turn, can reference other files. For more details - * about conference data synchronization, refer to the documentation at - * http://code.google.com/p/iosched. - * - * @param syncResult (optional) the sync result object to update with statistics. - * @param account the account associated with this sync - * @return Whether or not the synchronization made any changes to the data. - */ - public boolean performSync(SyncResult syncResult, Account account, Bundle extras) { - boolean dataChanged = false; - - if (!PrefUtils.isDataBootstrapDone(mContext)) { - LOGD(TAG, "Sync aborting (data bootstrap not done yet)"); - return false; - } - - long lastAttemptTime = PrefUtils.getLastSyncAttemptedTime(mContext); - long now = UIUtils.getCurrentTime(mContext); - long timeSinceAttempt = now - lastAttemptTime; - final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - final boolean userDataOnly = extras.getBoolean(SyncAdapter.EXTRA_SYNC_USER_DATA_ONLY, false); - - if (!manualSync && timeSinceAttempt >= 0 && timeSinceAttempt < Config.MIN_INTERVAL_BETWEEN_SYNCS) { - /* - Code removed because it was causing a runaway sync; probably because we are setting - syncResult.delayUntil incorrectly. - - Random r = new Random(); - long toWait = 10000 + r.nextInt(30000) // random jitter between 10 - 40 seconds - + Config.MIN_INTERVAL_BETWEEN_SYNCS - timeSinceAttempt; - LOGW(TAG, "Sync throttled!! Another sync was attempted just " + timeSinceAttempt - + "ms ago. Requesting delay of " + toWait + "ms."); - syncResult.fullSyncRequested = true; - syncResult.delayUntil = (System.currentTimeMillis() + toWait) / 1000L; - return false;*/ - } - - LOGI(TAG, "Performing sync for account: " + account); - PrefUtils.markSyncAttemptedNow(mContext); - long opStart; - long remoteSyncDuration, choresDuration; - - opStart = System.currentTimeMillis(); - - // remote sync consists of these operations, which we try one by one (and tolerate - // individual failures on each) - final int OP_REMOTE_SYNC = 0; - final int OP_USER_SCHEDULE_SYNC = 1; - final int OP_USER_FEEDBACK_SYNC = 2; - - int[] opsToPerform = userDataOnly ? - new int[] { OP_USER_SCHEDULE_SYNC } : - new int[] { OP_REMOTE_SYNC, OP_USER_SCHEDULE_SYNC, OP_USER_FEEDBACK_SYNC}; - - - for (int op : opsToPerform) { - try { - switch (op) { - case OP_REMOTE_SYNC: - dataChanged |= doRemoteSync(); - break; - case OP_USER_SCHEDULE_SYNC: - dataChanged |= doUserScheduleSync(account.name); - break; - case OP_USER_FEEDBACK_SYNC: - doUserFeedbackSync(); - break; - } - } catch (AuthException ex) { - syncResult.stats.numAuthExceptions++; - - // if we have a token, try to refresh it - if (AccountUtils.hasToken(mContext, account.name)) { - AccountUtils.refreshAuthToken(mContext); - } else { - LOGW(TAG, "No auth token yet for this account. Skipping remote sync."); - } - } catch (Throwable throwable) { - throwable.printStackTrace(); - LOGE(TAG, "Error performing remote sync."); - increaseIoExceptions(syncResult); - } - } - remoteSyncDuration = System.currentTimeMillis() - opStart; - - // If data has changed, there are a few chores we have to do - opStart = System.currentTimeMillis(); - if (dataChanged) { - try { - performPostSyncChores(mContext); - } catch (Throwable throwable) { - throwable.printStackTrace(); - LOGE(TAG, "Error performing post sync chores."); - } - } - clearExpertsIfNecessary(); - choresDuration = System.currentTimeMillis() - opStart; - - int operations = mConferenceDataHandler.getContentProviderOperationsDone(); - if (syncResult != null && syncResult.stats != null) { - syncResult.stats.numEntries += operations; - syncResult.stats.numUpdates += operations; - } - - if (dataChanged) { - long totalDuration = choresDuration + remoteSyncDuration; - LOGD(TAG, "SYNC STATS:\n" + - " * Account synced: " + (account == null ? "null" : account.name) + "\n" + - " * Content provider operations: " + operations + "\n" + - " * Remote sync took: " + remoteSyncDuration + "ms\n" + - " * Post-sync chores took: " + choresDuration + "ms\n" + - " * Total time: " + totalDuration + "ms\n" + - " * Total data read from cache: \n" + - (mRemoteDataFetcher.getTotalBytesReadFromCache() / 1024) + "kB\n" + - " * Total data downloaded: \n" + - (mRemoteDataFetcher.getTotalBytesDownloaded() / 1024) + "kB"); - } - - LOGI(TAG, "End of sync (" + (dataChanged ? "data changed" : "no data change") + ")"); - - updateSyncInterval(mContext, account); - - return dataChanged; - } - - public static void performPostSyncChores(final Context context) { - // Update search index - LOGD(TAG, "Updating search index."); - context.getContentResolver().update(ScheduleContract.SearchIndex.CONTENT_URI, - new ContentValues(), null, null); - - // Sync calendars - LOGD(TAG, "Session data changed. Syncing starred sessions with Calendar."); - syncCalendar(context); - } - - private static void syncCalendar(Context context) { - Intent intent = new Intent(SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR); - intent.setClass(context, SessionCalendarService.class); - context.startService(intent); - } - - private void doUserFeedbackSync() { - LOGD(TAG, "Syncing feedback"); - new FeedbackSyncHelper(mContext).sync(); - } - - /** - * Checks if the remote server has new data that we need to import. If so, download - * the new data and import it into the database. - * - * @return Whether or not data was changed. - * @throws IOException if there is a problem downloading or importing the data. - */ - private boolean doRemoteSync() throws IOException { - if (!isOnline()) { - LOGD(TAG, "Not attempting remote sync because device is OFFLINE"); - return false; - } - - LOGD(TAG, "Starting remote sync."); - - // Fetch the remote data files via RemoteConferenceDataFetcher - String[] dataFiles = mRemoteDataFetcher.fetchConferenceDataIfNewer( - mConferenceDataHandler.getDataTimestamp()); - - if (dataFiles != null) { - LOGI(TAG, "Applying remote data."); - // save the remote data to the database - mConferenceDataHandler.applyConferenceData(dataFiles, - mRemoteDataFetcher.getServerDataTimestamp(), true); - LOGI(TAG, "Done applying remote data."); - - // mark that conference data sync succeeded - PrefUtils.markSyncSucceededNow(mContext); - return true; - } else { - // no data to process (everything is up to date) - - // mark that conference data sync succeeded - PrefUtils.markSyncSucceededNow(mContext); - return false; - } - } - - /** - * Checks if there are changes on MySchedule to sync with/from remote AppData folder. - * - * @return Whether or not data was changed. - * @throws IOException if there is a problem uploading the data. - */ - private boolean doUserScheduleSync(String accountName) throws IOException { - if (!isOnline()) { - LOGD(TAG, "Not attempting myschedule sync because device is OFFLINE"); - return false; - } - - LOGD(TAG, "Starting user data (myschedule) sync."); - - AbstractUserDataSyncHelper helper = UserDataSyncHelperFactory.buildSyncHelper( - mContext, accountName); - boolean modified = helper.sync(); - if (modified) { - // schedule notifications for the starred sessions - Intent scheduleIntent = new Intent( - SessionAlarmService.ACTION_SCHEDULE_ALL_STARRED_BLOCKS, - null, mContext, SessionAlarmService.class); - mContext.startService(scheduleIntent); - } - return modified; - } - - // Returns whether we are connected to the internet. - private boolean isOnline() { - ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - return cm.getActiveNetworkInfo() != null && - cm.getActiveNetworkInfo().isConnectedOrConnecting(); - } - - private void increaseIoExceptions(SyncResult syncResult) { - if (syncResult != null && syncResult.stats != null) { - ++syncResult.stats.numIoExceptions; - } - } - - private void increaseSuccesses(SyncResult syncResult) { - if (syncResult != null && syncResult.stats != null) { - ++syncResult.stats.numEntries; - ++syncResult.stats.numUpdates; - } - } - - private boolean clearExpertsIfNecessary() { - if (Config.hasExpertsDirectoryExpired()) { - return 0 < mContext.getContentResolver() - .delete(ScheduleContract.Experts.CONTENT_URI, null, null); - } - return false; - } - - public static class AuthException extends RuntimeException { - } - - - public static long calculateRecommendedSyncInterval(final Context context) { - long now = UIUtils.getCurrentTime(context); - long aroundConferenceStart = Config.CONFERENCE_START_MILLIS - Config.AUTO_SYNC_AROUND_CONFERENCE_THRESH; - if (now < aroundConferenceStart) { - return Config.AUTO_SYNC_INTERVAL_LONG_BEFORE_CONFERENCE; - } else if (now <= Config.CONFERENCE_END_MILLIS) { - return Config.AUTO_SYNC_INTERVAL_AROUND_CONFERENCE; - } else { - return Config.AUTO_SYNC_INTERVAL_AFTER_CONFERENCE; - } - } - - public static void updateSyncInterval(final Context context, final Account account) { - LOGD(TAG, "Checking sync interval for " + account); - long recommended = calculateRecommendedSyncInterval(context); - long current = PrefUtils.getCurSyncInterval(context); - LOGD(TAG, "Recommended sync interval " + recommended + ", current " + current); - if (recommended != current) { - LOGD(TAG, "Setting up sync for account " + account + ", interval " + recommended + "ms"); - ContentResolver.setIsSyncable(account, ScheduleContract.CONTENT_AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, ScheduleContract.CONTENT_AUTHORITY, true); - ContentResolver.addPeriodicSync(account, ScheduleContract.CONTENT_AUTHORITY, - new Bundle(), recommended / 1000L); - PrefUtils.setCurSyncInterval(context, recommended); - } else { - LOGD(TAG, "No need to update sync interval."); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java deleted file mode 100755 index 5ca6c1f14c..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java +++ /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. - */ - -package com.google.samples.apps.iosched.sync; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -/** - * Service that handles sync. It simply instantiates a SyncAdapter and returns its IBinder. - */ -public class SyncService extends Service { - private static final Object sSyncAdapterLock = new Object(); - private static SyncAdapter sSyncAdapter = null; - - @Override - public void onCreate() { - synchronized (sSyncAdapterLock) { - if (sSyncAdapter == null) { - sSyncAdapter = new SyncAdapter(getApplicationContext(), false); - } - } - } - - @Override - public IBinder onBind(Intent intent) { - return sSyncAdapter.getSyncAdapterBinder(); - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java b/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java deleted file mode 100644 index bfb6630281..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java +++ /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. - */ - - -package com.google.samples.apps.iosched.sync; - -import android.accounts.Account; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; - -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.util.AccountUtils; - -/** - * A simple {@link BroadcastReceiver} that triggers a sync. This is used by the GCM code to trigger - * jittered syncs using {@link android.app.AlarmManager}. - */ -public class TriggerSyncReceiver extends BroadcastReceiver { - public static final String EXTRA_USER_DATA_SYNC_ONLY = "com.google.samples.apps.iosched.EXTRA_USER_DATA_SYNC_ONLY"; - - @Override - public void onReceive(Context context, Intent intent) { - String accountName = AccountUtils.getActiveAccountName(context); - if (TextUtils.isEmpty(accountName)) { - return; - } - Account account = AccountUtils.getActiveAccount(context); - if (account != null) { - if (intent.getBooleanExtra(EXTRA_USER_DATA_SYNC_ONLY, false) ) { - // this is a request to sync user data only, so do a manual sync right now - // with the userDataOnly == true. - SyncHelper.requestManualSync(account, true); - } else { - // this is a request to sync everything - ContentResolver.requestSync(account, ScheduleContract.CONTENT_AUTHORITY, new Bundle()); - } - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java deleted file mode 100644 index 5be93e098e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java +++ /dev/null @@ -1,55 +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 com.google.samples.apps.iosched.sync; - -import android.content.Context; - -import java.io.IOException; -import java.util.ArrayList; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGI; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - - -public class UserScheduleSyncHelper { - private Context mContext; - private static final String TAG = makeLogTag(SyncHelper.class); - - public UserScheduleSyncHelper(Context ctx) { - mContext = ctx; - } - - public void updateUserSchedule(Context context, ArrayList sessionsToAdd, - ArrayList sessionsToRemove) throws IOException { - LOGI(TAG, "Updating session on user schedule: add "+sessionsToAdd.size()+ - " and remove "+sessionsToRemove.size()+" sessions"); - /* - Googledevelopers conferenceAPI = getConferenceAPIClient(); - try { - sendScheduleUpdate(conferenceAPI, context, sessionId, inSchedule); - } catch (GoogleJsonResponseException e) { - if (e.getDetails().getCode() == 401) { - LOGI(TAG, "Unauthorized; getting a new auth token.", e); - AccountUtils.refreshAuthToken(mContext); - // Try request one more time with new credentials before giving up - conferenceAPI = getConferenceAPIClient(); - sendScheduleUpdate(conferenceAPI, context, sessionId, inSchedule); - } - } - */ - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java deleted file mode 100644 index 449f4c8bcf..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java +++ /dev/null @@ -1,172 +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 com.google.samples.apps.iosched.sync.userdata; - -import android.content.*; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - -import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider; -import com.google.samples.apps.iosched.gcm.ServerUtilities; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.provider.ScheduleContract.MySchedule; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.LOGW; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - - -/** - * Helper class that syncs starred sessions data in a Drive's AppData folder. - * - * Protocode: - * - * // when user clicks on "star": - * session UI: run updateSession() - * this.updateSession(): - * send addstar/removestar to contentProvider - * send broadcast to update any dependent UI - * save user actions as pending in shared preferences - * - * // on sync - * syncadapter: call this.sync() - * this.sync(): - * fetch remote content - * if pending actions: - * apply to content and update remote - * if modified content != last synced content: - * update contentProvider - * send broadcast to update any dependent UI - * - * - */ -public abstract class AbstractUserDataSyncHelper { - private static final String TAG = makeLogTag(AbstractUserDataSyncHelper.class); - - protected Context mContext; - protected String mAccountName; - - public AbstractUserDataSyncHelper(Context context, String accountName) { - this.mContext = context; - this.mAccountName = accountName; - } - - protected abstract boolean syncImpl(List actions, boolean hasPendingLocalData); - - /** - * Create a copy of current pending actions and delegate the - * proper sync'ing to the concrete subclass on the method syncImpl. - * - */ - public boolean sync() { - // get data pending sync: - Cursor scheduleData = mContext.getContentResolver().query( - MySchedule.buildMyScheduleUri(mContext, mAccountName), MyScheduleQuery.PROJECTION, - null, null, null); - - if (scheduleData == null) { - return false; - } - - // Although we have a dirty flag per item, we need all schedule to sync, because it's all - // sync'ed at once to a file on AppData folder. We only use the dirty flag to decide if - // the local content was changed or not. If it was, we replace the remote content. - boolean hasPendingLocalData = false; - ArrayList actions = new ArrayList(); - while (scheduleData.moveToNext()) { - - UserAction userAction = new UserAction(); - userAction.sessionId = scheduleData.getString(MyScheduleQuery.SESSION_ID); - Integer inSchedule = scheduleData.getInt(MyScheduleQuery.IN_SCHEDULE); - if (inSchedule == 0) { - userAction.type = UserAction.TYPE.REMOVE_STAR; - } else { - userAction.type = UserAction.TYPE.ADD_STAR; - } - userAction.requiresSync = scheduleData.getInt(MyScheduleQuery.DIRTY_FLAG) == 1; - actions.add(userAction); - if (!hasPendingLocalData && userAction.requiresSync) { - hasPendingLocalData = true; - } - } - scheduleData.close(); - - Log.d(TAG, "Starting Drive AppData sync. hasPendingData = " + hasPendingLocalData); - - boolean dataChanged = syncImpl(actions, hasPendingLocalData); - - if (hasPendingLocalData) { - resetDirtyFlag(actions); - - // Notify other devices via GCM - ServerUtilities.notifyUserDataChanged(mContext); - } - if (dataChanged) { - LOGD(TAG, "Notifying changes on paths related to user data on Content Resolver."); - ContentResolver resolver = mContext.getContentResolver(); - for (String path : ScheduleContract.USER_DATA_RELATED_PATHS) { - Uri uri = ScheduleContract.BASE_CONTENT_URI.buildUpon().appendPath(path).build(); - resolver.notifyChange(uri, null); - } - mContext.sendBroadcast(ScheduleWidgetProvider.getRefreshBroadcastIntent(mContext, false)); - } - return dataChanged; - } - - private void resetDirtyFlag(ArrayList actions) { - ArrayList ops = new ArrayList(); - for (UserAction action: actions) { - ContentProviderOperation op = ContentProviderOperation.newUpdate( - ScheduleContract.addCallerIsSyncAdapterParameter( - MySchedule.buildMyScheduleUri(mContext, mAccountName))) - .withSelection(MySchedule.SESSION_ID + "=? AND " + - MySchedule.MY_SCHEDULE_IN_SCHEDULE + "=?", - new String[]{action.sessionId, - action.type == UserAction.TYPE.ADD_STAR ? "1" : "0"}) - .withValue(MySchedule.MY_SCHEDULE_DIRTY_FLAG, 0) - .build(); - LOGD(TAG, op.toString()); - ops.add(op); - } - try { - ContentProviderResult[] result = mContext.getContentResolver().applyBatch( - ScheduleContract.CONTENT_AUTHORITY, ops); - LOGD(TAG, "Result of cleaning dirty flags is "+ Arrays.toString(result)); - } catch (Exception ex) { - LOGW(TAG, "Could not update dirty flags. Ignoring.", ex); - } - } - - private interface MyScheduleQuery { - - String[] PROJECTION = { - MySchedule.SESSION_ID, - MySchedule.MY_SCHEDULE_IN_SCHEDULE, - MySchedule.MY_SCHEDULE_DIRTY_FLAG, - }; - - int SESSION_ID = 0; - int IN_SCHEDULE= 1; - int DIRTY_FLAG = 2; - } - -} \ No newline at end of file diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java deleted file mode 100644 index d1378beb2d..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java +++ /dev/null @@ -1,20 +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 com.google.samples.apps.iosched.sync.userdata; - -public interface OnSuccessListener { - void onSuccess(); -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java deleted file mode 100644 index bd8640be09..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java +++ /dev/null @@ -1,36 +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 com.google.samples.apps.iosched.sync.userdata; - -public class UserAction { - public enum TYPE { - ADD_STAR, REMOVE_STAR; - }; - - public UserAction() { - } - - public UserAction(TYPE type, String sessionId) { - this.type = type; - this.sessionId = sessionId; - } - - public TYPE type; - public String sessionId; - public String accountName; - public boolean requiresSync; - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java deleted file mode 100644 index 71a3ded368..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java +++ /dev/null @@ -1,36 +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 com.google.samples.apps.iosched.sync.userdata; - -import android.content.Context; - -import com.google.samples.apps.iosched.sync.userdata.http.HTTPUserDataSyncHelper; - - -/** - * A simple factory to isolate the decision of which synchelper should be used. - * - * Currently, the HTTP sync helper is always returned, because of the early stage of - * the GMS version. In the future, this can be changed. - * -**/ - -public class UserDataSyncHelperFactory { - public static AbstractUserDataSyncHelper buildSyncHelper(Context context, String accountName) { - return new HTTPUserDataSyncHelper(context, accountName); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java deleted file mode 100644 index 7e1ad718e7..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java +++ /dev/null @@ -1,142 +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 com.google.samples.apps.iosched.sync.userdata.gms; - -import com.google.samples.apps.iosched.util.AccountUtils; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; -import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; -import com.google.android.gms.drive.Drive; - -import android.app.Activity; -import android.content.Context; -import android.content.IntentSender; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; - -import java.util.concurrent.CountDownLatch; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * An AsyncTask that maintains a connected client. - */ -public abstract class ApiClientAsyncTask - extends AsyncTask { - - private static final String TAG = makeLogTag(ApiClientAsyncTask.class); - private static final int REQUEST_CODE_RESOLUTION = 1; - - private GoogleApiClient mClient; - private Context mContext; - private String lastUsedAccountName; - - public ApiClientAsyncTask(Context context) { - this.mContext = context; - } - - @Override - protected final Result doInBackground(Params... params) { - Log.d(TAG, "doInBackground of ApiClientAsyncTask"); - - getGoogleApiClient(); - - final CountDownLatch latch = new CountDownLatch(1); - mClient.registerConnectionCallbacks(new ConnectionCallbacks() { - @Override - public void onConnectionSuspended(int cause) { - } - - @Override - public void onConnected(Bundle arg0) { - Log.d(TAG, "ApiClientAsyncTask onConnected"); - latch.countDown(); - } - }); - mClient.registerConnectionFailedListener(new OnConnectionFailedListener() { - @Override - public void onConnectionFailed(ConnectionResult result) { - Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); - if (!result.hasResolution()) { - // show the localized error dialog. - GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), - (Activity) ApiClientAsyncTask.this.getContext(), 0).show(); - return; - } - try { - result.startResolutionForResult((Activity) ApiClientAsyncTask.this.getContext(), REQUEST_CODE_RESOLUTION); - } catch (IntentSender.SendIntentException e) { - Log.e(TAG, "Exception while starting resolution activity", e); - } - latch.countDown(); - } - }); - mClient.connect(); - try { - latch.await(); - } catch (InterruptedException e) { - return null; - } - if (!mClient.isConnected()) { - return null; - } - try { - return doInBackgroundConnected(params); - } catch (RuntimeException e) { - Log.e(TAG, "ApiClientAsyncTask exception on doInBackgroundConnected!", e); - throw e; - } finally { - mClient.disconnect(); - } - } - - /** - * Override this method to perform a computation on a background thread, while the client is - * connected. - */ - protected abstract Result doInBackgroundConnected(Params... params); - - /** - * Gets the GoogleApliClient owned by this async task. - */ - protected GoogleApiClient getGoogleApiClient() { - String currentAccountName = AccountUtils.getActiveAccountName(mContext); - if (lastUsedAccountName != null && - !lastUsedAccountName.equals(currentAccountName)) { - if (mClient != null && mClient.isConnected()) { - mClient.disconnect(); - } - mClient = null; - lastUsedAccountName = currentAccountName; - } - if (mClient == null) { - GoogleApiClient.Builder builder = new GoogleApiClient.Builder(mContext) - .addApi(Drive.API) - .setAccountName(currentAccountName) - .addScope(Drive.SCOPE_APPFOLDER); - mClient = builder.build(); - } - mClient.connect(); - return mClient; - } - - public Context getContext() { - return mContext; - } -} \ No newline at end of file diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java deleted file mode 100644 index 64c9cc8ade..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java +++ /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. - */ -package com.google.samples.apps.iosched.sync.userdata.gms; - -import android.content.Context; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.drive.*; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * - * Async task that syncs a file with the Drive AppData folder. - * - **/ -public class DriveAppAsyncTask extends ApiClientAsyncTask { - - public DriveAppAsyncTask(Context context) { - super(context); - } - - /* - private static final String TAG = makeLogTag(DriveAppAsyncTask.class); - private SyncParams mParams; - - protected String getDriveID() { - return mParams == null ? null : mParams.getDriveId(); - } - - protected Date getLastModifiedDate() { - return mParams == null ? null : mParams.getLastModifiedDate(); - } - -*/ - @Override - protected Boolean doInBackgroundConnected(Void... params) { - return true; - } - /* - Log.d(TAG, "on DriveAppAsyncTask"); - this.mParams = params[0]; - GoogleApiClient apiClient = getGoogleApiClient(); - Set ourContent = UserDataHelper.getLocalStarredSessionIDs(getContext()); - - boolean requiresUIRefresh = false; - - DriveId currentDriveId = mParams.getDriveId() == null ? null : DriveId.decodeFromString(mParams.getDriveId()); - DriveFile file = DriveHelper.lookupDriveFile(currentDriveId, apiClient); - mParams.setDriveId(file == null ? null : file.getDriveId().encodeToString()); - - - try { - if (file == null) { - // File doesn't exist in Drive - Log.d(TAG, "Creating file on Drive"); - DriveHelper.createNewDriveFile(mParams, ourContent, apiClient); - - } else { - // File exists in Drive - boolean requiresCloudUpdate = false; - - /** It seems that, due to a bug on AppData GMS implementation, the metadata is not - * being updated correctly when something changes in the cloud. So, we are removing - * the fancy logic and keeping it to the bare minimum. - // Compare last modified date - Date lastModifiedCloud = metadata.getModifiedDate(); - Log.d(TAG, "Found file in Drive ID="+file.getDriveId()+ - " cloud_last_modified="+lastModifiedCloud+ - " local_last_modified="+param.getLastModifiedDate()); - int dataCmp = lastModifiedCloud.compareTo(param.getLastModifiedDate()); - Log.d(TAG, "dataCmp="+dataCmp+" hasPendingActions="+param.hasPendingActions()); - - if (dataCmp > 0) { - * * / - // If cloud file is newer than ours, merge our content there - Log.d(TAG, "File in cloud is newer than ours. Maybe merging."); - Set cloudContent = DriveHelper.loadFromCloud(file, apiClient); - if (mParams.hasPendingActions()) { - // apply our pending actions on top of Drive contents - Log.d(TAG, "Local pending actions, applying to the remote file."); - if (!cloudContent.equals(ourContent)) { - for (UserAction action: mParams.getPendingActions()) { - Log.d(TAG, "Applying action "+action); - if (action.type == UserAction.TYPE.ADD_STAR) { - cloudContent.add(action.sessionId); - } else { - cloudContent.remove(action.sessionId); - } - } - requiresCloudUpdate = true; - } - } - - if (!cloudContent.equals(ourContent)) { - ourContent = cloudContent; - UserDataHelper.setLocalStarredSessions(getContext(), cloudContent); - requiresUIRefresh = true; - } - - /** - } else if (dataCmp < 0 || ( dataCmp == 0 && param.hasPendingActions())) { - // Replace Drive contents with our content - Log.d(TAG, "File in cloud is behind ours. Will replace it."); - requiresCloudUpdate = true; - } - * * / - if (requiresCloudUpdate) { - DriveHelper.saveToDrive(file, ourContent, apiClient); - //lastModifiedCloud = getLastModifiedDate(file, apiClient); - } - // param.setLastModifiedDate(lastModifiedCloud); - mParams.setLastModifiedDate(new Date()); - } - - } catch (IOException e) { - Log.e(TAG, "IOException while setting content to the new file", e); - throw new RuntimeException("IOException while setting content to the new file", e); - } - - return requiresUIRefresh; - } - - private Date getLastModifiedDate(DriveFile file, GoogleApiClient apiClient) { - DriveResource.MetadataResult result = file.getMetadata(apiClient).await(); - DriveHelper.checkStatus("Getting last modified date", result.getStatus()); - return result.getMetadata().getModifiedDate(); - } - -*/ -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java deleted file mode 100644 index d5c74798e9..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java +++ /dev/null @@ -1,172 +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 com.google.samples.apps.iosched.sync.userdata.gms; - -import android.util.Log; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.drive.*; -import com.google.api.client.util.Charsets; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -import static com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper.*; -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class DriveHelper { - - private static final String TAG = makeLogTag(DriveHelper.class); - - // Constants related to the JSON serialization: - private static final String MIMETYPE_JSON = "application/json"; - public static final String DRIVE_FILENAME = "starred_sessions.json"; - - static void saveToDrive(DriveFile file, Set contents, - GoogleApiClient apiClient) throws IOException { - DriveApi.ContentsResult contentsResult = file.openContents(apiClient, - DriveFile.MODE_WRITE_ONLY, null).await(); - checkStatus("Open file for writing", contentsResult.getStatus()); - FileOutputStream os = new FileOutputStream(contentsResult.getContents() - .getParcelFileDescriptor().getFileDescriptor()); - byte[] serializedContents = toByteArray(contents); - Log.d(TAG, "Saving contents to drive file: "+new String(serializedContents)); - os.write(serializedContents); - com.google.android.gms.common.api.Status status = - file.commitAndCloseContents(apiClient, contentsResult.getContents()).await(); - checkStatus("Commit file contents", status); - } - - static public DriveFile lookupDriveFile(DriveId driveId, GoogleApiClient apiClient) { - DriveFile result = null; - - // First, check if ID is valid - if (driveId != null) { - Log.d(TAG, "DriveID passed is not null, trying to get the corresponding file"); - try { - result = Drive.DriveApi.getFile(apiClient, driveId); - if (result != null) { - // check if metadata is ok. For example, if the file has been directly removed from - // the server, the getFile can return a file that is actually not valid. Hopefully - // the metadata will get the correct info - try { - DriveResource.MetadataResult metadataResult = result.getMetadata(apiClient).await(); - if (!metadataResult.getStatus().isSuccess()) { - result = null; - } - } catch (Exception ex) { - result = null; - } - } - } catch (Exception e) { - Log.d(TAG, "Saved drive ID "+driveId+" seems to be invalid (message: " + - e.getMessage()+"). Ignoring it"); - result = null; - } - } - - if (result == null) { - // search for a file with the expected name (and get the most recent one, if many) - Log.d(TAG, "DriveID passed is null, looking up for a file named "+DRIVE_FILENAME); - Metadata metaOfMostRecent = null; - MetadataBuffer buffer = Drive.DriveApi.getAppFolder(apiClient) - .listChildren(apiClient).await().getMetadataBuffer(); - Log.d(TAG, "Found "+buffer.getCount()+" files"); - for (Metadata metadata: buffer) { - if (metaOfMostRecent != null) { - Log.w(TAG, "Warning, found more than one file named "+DRIVE_FILENAME+ - " in AppData folder. Using the most recently modified."); - } - if (metaOfMostRecent == null || metaOfMostRecent - .getModifiedDate().compareTo(metadata.getModifiedDate())<0) { - metaOfMostRecent = metadata; - } - } - if (metaOfMostRecent != null) { - driveId = metaOfMostRecent.getDriveId(); - result = Drive.DriveApi.getFile(apiClient, driveId); - } - buffer.close(); - } - - return result; - } - - static void createNewDriveFile(Set contents, - GoogleApiClient apiClient) throws IOException { - - DriveApi.ContentsResult contentsResult = Drive.DriveApi.newContents(apiClient).await(); - checkStatus("creating new file", contentsResult.getStatus()); - - // query Drive for an AppFolder reference (might be slow: ~4s in my tests) - DriveFolder appDataFolder = Drive.DriveApi.getAppFolder(apiClient); - - // create a new file in AppFolder - MetadataChangeSet metadataChangeSet = - new MetadataChangeSet.Builder() - .setMimeType(MIMETYPE_JSON) - .setTitle(DRIVE_FILENAME) - .build(); - Contents contentsObj = contentsResult.getContents(); - - FileOutputStream os = new FileOutputStream(contentsObj.getParcelFileDescriptor().getFileDescriptor()); - os.write(toByteArray(contents)); - - DriveFolder.DriveFileResult fileResult = appDataFolder.createFile( - apiClient, metadataChangeSet, contentsResult.getContents()).await(); - - Log.d(TAG, "Content saved to new Drive file: "+new String(toByteArray(contents), - Charsets.UTF_8)); - checkStatus("saving contents to new file", fileResult.getStatus()); - - // DON'T DO THIS: It seems that a bug makes this driveID being unusable later: - // params.setDriveId(fileResult.getDriveFile().getDriveId()); - } - - static public void checkStatus(String message, com.google.android.gms.common.api.Status status) { - if (!status.isSuccess()) { - throw new RuntimeException("Error "+status.getStatusCode()+" on "+message); - } - } - - static public Set loadFromCloud(DriveFile file, GoogleApiClient apiClient) - throws IOException { - DriveApi.ContentsResult contentsResult = file.openContents(apiClient, - DriveFile.MODE_READ_ONLY, null).await(); - checkStatus("Open file for reading", contentsResult.getStatus()); - - HashSet result = new HashSet(); - try { - FileInputStream is = new FileInputStream(contentsResult.getContents() - .getParcelFileDescriptor().getFileDescriptor()); - String contents = fromStreamToString(is); - file.discardContents(apiClient, contentsResult.getContents()); - - LOGD(TAG, "Contents in the cloud file: [" + contents + "]"); - - return fromString(contents); - - } catch (Exception ex) { - Log.w(TAG, "Ignoring invalid remote content.", ex); - return null; - } - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java deleted file mode 100644 index bbb315d3af..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java +++ /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. - */ - -package com.google.samples.apps.iosched.sync.userdata.gms; - -import android.content.Context; - -import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper; -import com.google.samples.apps.iosched.sync.userdata.UserAction; - -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - - -/** - * Helper class that syncs starred sessions data with Drive's AppData folder using GMS - * support for Drive AppData folder. - */ -public class GMSUserDataSyncHelper extends AbstractUserDataSyncHelper { - private static final String TAG = makeLogTag(GMSUserDataSyncHelper.class); - - public GMSUserDataSyncHelper(Context context, String accountName) { - super(context, accountName); - } - - @Override - protected boolean syncImpl(List actions, boolean hasPendingLocalData) { - /* - DriveAppAsyncTask task = new DriveAppAsyncTask(getContext()) { - @Override - protected void onPostExecute(Boolean requiresUIRefresh) { - Log.d(TAG, "Finished DriveAppAsyncTask. RequiresUIRefresh = " + requiresUIRefresh); - updateSharedPreferences(getDriveID(), getLastModifiedDate()); - if (requiresUIRefresh != null && requiresUIRefresh) { - notifyDataHasChanged(); - } - if (callback != null) { - callback.onSuccess(); - } - } - }; - task.execute(params);*/ - throw new RuntimeException("Unsupported implementation of GMSUserDataSyncHelper"); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java deleted file mode 100644 index b105c7af5a..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java +++ /dev/null @@ -1,122 +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 com.google.samples.apps.iosched.sync.userdata.http; - -import android.util.Log; - -import com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper; -import com.google.api.client.http.ByteArrayContent; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpResponse; -import com.google.api.services.drive.Drive; -import com.google.api.services.drive.model.File; -import com.google.api.services.drive.model.FileList; -import com.google.api.services.drive.model.ParentReference; - -import java.io.IOException; -import java.util.Arrays; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Provides fundamental base abstractions for handling files in App Data - * - */ -public abstract class DriveTask { - - private static final String TAG = makeLogTag(DriveTask.class); - private Drive mDriveService = null; - - final public static String FILE_NAME = "starred_sessions.json"; - final public static String FILE_MIME_TYPE = "application/json"; - - /** - * Constructs an object. - * @param driveService - */ - public DriveTask(Drive driveService) { - mDriveService = driveService; - } - - /** - * Getter for the Drive service. - */ - public Drive getDriveService() { - return mDriveService; - } - - /** - * Inserts preferences file into the appdata folder. - * @param content The application context. - * @return Inserted file. - * @throws IOException - */ - public File insertPreferencesFile(String content) throws IOException { - File metadata = new File(); - metadata.setTitle(FILE_NAME); - metadata.setParents(Arrays.asList(new ParentReference().setId("appdata"))); - ByteArrayContent c = - ByteArrayContent.fromString(FILE_MIME_TYPE, content); - return mDriveService.files().insert(metadata, c).execute(); - } - - /** - * Updates the preferences file with content. - * @param file File metadata. - * @param content File content in JSON. - * @return Updated file. - * @throws IOException - */ - public File updatePreferencesFile(File file, String content) - throws IOException { - Log.d(TAG, "Saving content to remote drive "+file.getId()+" : [" + content + "]"); - ByteArrayContent c = - ByteArrayContent.fromString(FILE_MIME_TYPE, content); - return mDriveService.files().update(file.getId(), file, c).execute(); - } - - /** - * Retrieves the preferences file from the appdata folder. - * @return Retrieved preferences file or {@code null}. - * @throws IOException - */ - public File getOrCreateFile() throws IOException { - // TODO: fix the contains query once title querying bug is being resolved. - String query = - "title contains '" + FILE_NAME + "' and 'appdata' in parents"; - FileList list = mDriveService.files().list().setQ(query).execute(); - if (list != null && list.getItems().size() > 0) { - return list.getItems().get(0); - } else { - // create a new preferences file - return insertPreferencesFile("{\"starred_sessions\": []}"); - } - } - - /** - * Downloads the file contents. - * @param file File to download. - * @return The file content.l - * @throws IOException - */ - public String downloadFile(File file) throws IOException { - HttpResponse res = mDriveService.getRequestFactory() - .buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute(); - return UserDataHelper.fromStreamToString(res.getContent()); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java deleted file mode 100644 index ecd636819b..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java +++ /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. - */ - -package com.google.samples.apps.iosched.sync.userdata.http; - -import java.io.IOException; - -import com.google.api.services.drive.Drive; -import com.google.api.services.drive.model.File; - -/** - * Gets or creates a file on user's appdata folder. - * - */ -public class GetOrCreateFIleDriveTask extends DriveTask { - - /** - * Constructs a new get or create task. - * @param driveService A drive service. - */ - public GetOrCreateFIleDriveTask(Drive driveService) { - super(driveService); - } - - /** - * Executes the request.. - * @return Remote file's content. - * @throws IOException - */ - public String execute() throws IOException { - File file = getOrCreateFile(); - if (file.getDownloadUrl() != null) { - // retrieve the content - return downloadFile(file); - } - return null; - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java deleted file mode 100644 index 6a0ac4eab7..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java +++ /dev/null @@ -1,208 +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 com.google.samples.apps.iosched.sync.userdata.http; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import com.google.api.client.extensions.android.http.AndroidHttp; -import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; -import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.services.drive.Drive; -import com.google.api.services.drive.DriveScopes; -import com.google.samples.apps.iosched.sync.SyncHelper; -import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper; -import com.google.samples.apps.iosched.sync.userdata.UserAction; -import com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper; -import com.google.samples.apps.iosched.util.AccountUtils; - -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static com.google.samples.apps.iosched.util.LogUtils.LOGD; -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -/** - * Helper class that syncs starred sessions data with Drive's AppData folder using direct - * HTTP Drive API through google-api-client library. - * - * Based on https://github.com/googledrive/appdatapreferences-android - */ -public class HTTPUserDataSyncHelper extends AbstractUserDataSyncHelper { - private static final String GCM_KEY_PREFIX = "GCM:"; - - private GoogleAccountCredential mCredentials; - - /** - * Private {@code HTTPUserDataSyncHelper} constructor. - * @param context Context of the application - */ - public HTTPUserDataSyncHelper(Context context, String accountName) { - super(context, accountName); - mCredentials = GoogleAccountCredential.usingOAuth2(mContext, - java.util.Arrays.asList(DriveScopes.DRIVE_APPDATA)); - mCredentials.setSelectedAccountName(mAccountName); - } - - private String extractGcmKey(Set remote) { - String remoteGcmKey = null; - Set toRemove = new HashSet(); - for (String s : remote) { - if (s.startsWith(GCM_KEY_PREFIX)) { - toRemove.add(s); - remoteGcmKey = s.substring(GCM_KEY_PREFIX.length()); - LOGD(TAG, "Remote data came with GCM key: " - + AccountUtils.sanitizeGcmKey(remoteGcmKey)); - } - } - for (String s : toRemove) { - remote.remove(s); - } - return remoteGcmKey; - } - - /** - * Syncs the preferences file with an appdata preferences file. - * - * Synchronization steps: - * 1. If there are local changes, sync the latest local version with remote - * and ignore merge conflicts. The last write wins. - * 2. If there are no local changes, fetch the latest remote version. If - * it includes changes, notify that preferences have changed. - */ - protected boolean syncImpl(List actions, boolean hasPendingLocalData) { - try { - LOGD(TAG, "Now syncing user data."); - Set remote = UserDataHelper.fromString(fetchRemote()); - Set local = UserDataHelper.getSessionIDs(actions); - - String remoteGcmKey = extractGcmKey(remote); - String localGcmKey = AccountUtils.getGcmKey(mContext, mAccountName); - LOGD(TAG, "Local GCM key: " + AccountUtils.sanitizeGcmKey(localGcmKey)); - LOGD(TAG, "Remote GCM key: " + (remoteGcmKey == null ? "(null)" - : AccountUtils.sanitizeGcmKey(remoteGcmKey))); - - // if the remote data came with a GCM key, it should override ours - if (!TextUtils.isEmpty(remoteGcmKey)) { - if (remoteGcmKey.equals(localGcmKey)) { - LOGD(TAG, "Remote GCM key is the same as local, so no action necessary."); - } else { - LOGD(TAG, "Remote GCM key is different from local. OVERRIDING local."); - localGcmKey = remoteGcmKey; - AccountUtils.setGcmKey(mContext, mAccountName, localGcmKey); - } - } - - // If remote data is the same as local, and the remote end already has a GCM key, - // there is nothing we need to do. - if (remote.equals(local) && !TextUtils.isEmpty(remoteGcmKey)) { - LOGD(TAG, "Update is not needed (local is same as remote, and remote has key)"); - return false; - } - - Set merged; - if (hasPendingLocalData || TextUtils.isEmpty(remoteGcmKey)) { - // merge local dirty actions into remote content - if (hasPendingLocalData) { - LOGD(TAG, "Has pending local data, merging."); - merged = mergeDirtyActions(actions, remote); - } else { - LOGD(TAG, "No pending local data, just updating remote GCM key."); - merged = remote; - } - // add the GCM key special item - merged.add(GCM_KEY_PREFIX + localGcmKey); - // save to remote - LOGD(TAG, "Sending user data to Drive, gcm key " - + AccountUtils.sanitizeGcmKey(localGcmKey)); - new UpdateFileDriveTask(getDriveService()).execute( - UserDataHelper.toSessionsString(merged)); - } else { - merged = remote; - } - - UserDataHelper.setLocalStarredSessions(mContext, merged, mAccountName); - return true; - } catch (IOException e) { - handleException(e); - } - return false; - } - - /** - * Constructs a Drive service in the current context and with the - * credentials use to initiate AppdataPreferences instance. - * @return Drive service instance. - */ - public Drive getDriveService() { - Drive service = new Drive.Builder( - AndroidHttp.newCompatibleTransport(), - new GsonFactory(), mCredentials) - .setApplicationName(mContext.getApplicationInfo().name) - .build(); - return service; - } - - /** - * Updates the remote preferences file with the given JSON content. - * @throws IOException - */ - private Set mergeDirtyActions(List actions, Set starredSessions) - throws IOException { - // apply "dirty" actions: - for (UserAction action: actions) { - if (action.requiresSync) { - if (UserAction.TYPE.ADD_STAR.equals(action.type)) { - starredSessions.add(action.sessionId); - } else { - starredSessions.remove(action.sessionId); - } - } - } - return starredSessions; - } - - /** - * Fetches the remote file. - * @throws IOException - */ - private String fetchRemote() throws IOException { - String json = new GetOrCreateFIleDriveTask(getDriveService()).execute(); - Log.v(TAG, "Got this content from remote myschedule: ["+json+"]"); - return json; - } - - - /** - * Handles API exceptions and notifies OnExceptionListener - * if given exception is a UserRecoverableAuthIOException. - * @param exception Exception to handle - */ - private void handleException(Exception exception) { - Log.e(TAG, "Could not sync myschedule", exception); - if (exception != null && exception instanceof UserRecoverableAuthIOException) { - throw new SyncHelper.AuthException(); - } - } - - private static final String TAG = makeLogTag(HTTPUserDataSyncHelper.class); - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java deleted file mode 100644 index e3abdff639..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java +++ /dev/null @@ -1,50 +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 com.google.samples.apps.iosched.sync.userdata.http; - -import java.io.IOException; - -import com.google.api.services.drive.Drive; -import com.google.api.services.drive.model.File; - -/** - * Updates a string content to a file on appdata folder. - * - */ -public class UpdateFileDriveTask extends DriveTask { - - /** - * Constructs a new task. - * @param driveService A drive service. - */ - public UpdateFileDriveTask(Drive driveService) { - super(driveService); - } - - /** - * Executes the request. - * @param content The new file content. - * @throws IOException - */ - public void execute(String content) throws IOException { - // updates the existing preferences file with - // the preferences - File preferences = getOrCreateFile(); - updatePreferencesFile(preferences, content); - } - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java deleted file mode 100644 index 84404f5e0b..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java +++ /dev/null @@ -1,116 +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 com.google.samples.apps.iosched.sync.userdata.util; - -import android.content.ContentProviderOperation; -import android.content.Context; -import android.content.OperationApplicationException; -import android.os.RemoteException; -import android.util.Log; - -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.sync.userdata.UserAction; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.stream.JsonReader; - -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class UserActionHelper { - private static final String TAG = makeLogTag(UserActionHelper.class); - - static public void updateContentProvider(Context context, List userActions, String account) { - ArrayList batch = new ArrayList(); - for (UserAction action: userActions) { - batch.add(createUpdateOperation(context, action, account)); - } - try { - context.getContentResolver().applyBatch(ScheduleContract.CONTENT_AUTHORITY, batch); - } catch (RemoteException e) { - Log.e(TAG, "Could not apply operations", e); - } catch (OperationApplicationException e) { - Log.e(TAG, "Could not apply operations", e); - } - } - - static private ContentProviderOperation createUpdateOperation(Context context, UserAction action, String account) { - if (action.type == UserAction.TYPE.ADD_STAR) { - return ContentProviderOperation - .newInsert( - ScheduleContract.addOverrideAccountName( - ScheduleContract.MySchedule.CONTENT_URI, account)) - .withValue(ScheduleContract.MySchedule.MY_SCHEDULE_DIRTY_FLAG, "0") - .withValue(ScheduleContract.MySchedule.SESSION_ID, action.sessionId) - .build(); - } else { - return ContentProviderOperation - .newDelete( - ScheduleContract.addOverrideAccountName( - ScheduleContract.MySchedule.CONTENT_URI, account)) - .withSelection( - ScheduleContract.MySchedule.SESSION_ID + " = ? AND " + - ScheduleContract.MySchedule.MY_SCHEDULE_ACCOUNT_NAME + " = ? ", - new String[]{action.sessionId, account} - ) - .build(); - } - } - - public static String serializeUserActions(List actions) { - JsonArray array = new JsonArray(); - for (UserAction action: actions) { - JsonObject obj = new JsonObject(); - obj.add("type", new JsonPrimitive(action.type.name())); - obj.add("id", new JsonPrimitive(action.sessionId)); - array.add(obj); - } - return array.toString(); - } - - public static List deserializeUserActions(String str) { - try { - ArrayList actions = new ArrayList(); - JsonReader reader = new JsonReader(new StringReader(str)); - reader.beginArray(); - while (reader.hasNext()) { - reader.beginObject(); - UserAction action = new UserAction(); - while (reader.hasNext()) { - String key = reader.nextName(); - if ("type".equals(key)) { - action.type = UserAction.TYPE.valueOf(reader.nextString()); - } else if ("id".equals(key)) { - action.sessionId = reader.nextString(); - } else { - throw new RuntimeException("Invalid key "+key+" in serialized UserAction: "+str); - } - } - reader.endObject(); - actions.add(action); - } - reader.endArray(); - return actions; - } catch (IOException ex) { - throw new RuntimeException("Error deserializing UserActions: "+str, ex); - } - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java deleted file mode 100644 index 2dd1bd2be2..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java +++ /dev/null @@ -1,151 +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 com.google.samples.apps.iosched.sync.userdata.util; - -import android.content.Context; -import android.database.Cursor; -import android.util.Log; - -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.sync.userdata.UserAction; -import com.google.api.client.util.Charsets; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.stream.JsonReader; - -import java.io.*; -import java.util.*; - -import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; - -public class UserDataHelper { - - private static final String TAG = makeLogTag(UserDataHelper.class); - - // Constants related to the JSON serialization: - static final String JSON_STARRED_SESSIONS_KEY = "starred_sessions"; - - static public String toSessionsString(Set sessionIds) { - JsonArray array = new JsonArray(); - for (String sessionId: sessionIds) { - array.add(new JsonPrimitive(sessionId)); - } - JsonObject obj = new JsonObject(); - obj.add(JSON_STARRED_SESSIONS_KEY, array); - return obj.toString(); - } - - static public byte[] toByteArray(Set sessionIds) { - return toSessionsString(sessionIds).getBytes(Charsets.UTF_8); - } - - - static public Set fromString(String str) { - TreeSet result = new TreeSet(); - if (str == null || str.isEmpty()) { - return result; - } - try { - JsonReader reader = new JsonReader(new StringReader(str)); - reader.beginObject(); - while (reader.hasNext()) { - String key = reader.nextName(); - if (JSON_STARRED_SESSIONS_KEY.equals(key)) { - reader.beginArray(); - while (reader.hasNext()) { - result.add(reader.nextString()); - } - reader.endArray(); - } else { - reader.skipValue(); - } - } - reader.endObject(); - reader.close(); - } catch (Exception ex) { - Log.w(TAG, "Ignoring invalid remote content.", ex); - return null; - } - return result; - } - - static public SortedSet getSessionIDs(List actions) { - TreeSet starredSessions = new TreeSet(); - if (actions != null) { - for (UserAction action : actions) { - if (action.type == UserAction.TYPE.ADD_STAR) { - starredSessions.add(action.sessionId); - } - } - } - return starredSessions; - } - - static public Set getLocalStarredSessionIDs(Context context) { - Cursor sessionsCursor = context.getContentResolver().query( - ScheduleContract.MySchedule.CONTENT_URI, - new String[]{ScheduleContract.MySchedule.SESSION_ID}, null, null, null); - Set starredSessions = new HashSet(); - while (sessionsCursor.moveToNext()) { - starredSessions.add(sessionsCursor.getString(0)); - } - sessionsCursor.close(); - return starredSessions; - } - - static public void setLocalStarredSessions(Context context, Set sessionIds, String accountName) { - // first clear all stars - context.getContentResolver().delete(ScheduleContract.MySchedule.CONTENT_URI, - ScheduleContract.MySchedule.MY_SCHEDULE_ACCOUNT_NAME +" = ?", new String[]{accountName}); - - // now add only the ones in sessionIds - ArrayList actions = new ArrayList(); - for (String sessionId: sessionIds) { - UserAction action = new UserAction(); - action.type = UserAction.TYPE.ADD_STAR; - action.sessionId = sessionId; - actions.add(action); - } - UserActionHelper.updateContentProvider(context, actions, accountName); - } - - - /** - * Reads an input stream line by line and converts it into String. - * @param inputStream - * @throws IOException - */ - public static String fromStreamToString(InputStream inputStream) - throws IOException { - BufferedReader bufferedReader = null; - StringBuilder stringBuilder = new StringBuilder(); - try { - String line; - bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charsets.UTF_8)); - while ((line = bufferedReader.readLine()) != null) { - stringBuilder.append(line); - } - } finally { - if (bufferedReader != null) { - bufferedReader.close(); - } - } - return stringBuilder.toString(); - } - - -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java b/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java deleted file mode 100644 index 681503c48e..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java +++ /dev/null @@ -1,1466 +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 com.google.samples.apps.iosched.ui; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.animation.*; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.*; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Color; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.support.v4.widget.DrawerLayout; -import android.text.TextUtils; -import android.view.*; -import android.view.animation.DecelerateInterpolator; -import android.widget.*; - -import com.google.android.gcm.GCMRegistrar; -import com.google.android.gms.auth.GoogleAuthUtil; -import com.google.samples.apps.iosched.BuildConfig; -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.gcm.ServerUtilities; -import com.google.samples.apps.iosched.io.JSONHandler; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.sync.ConferenceDataHandler; -import com.google.samples.apps.iosched.sync.SyncHelper; -import com.google.samples.apps.iosched.ui.debug.DebugActionRunnerActivity; -import com.google.samples.apps.iosched.ui.widget.MultiSwipeRefreshLayout; -import com.google.samples.apps.iosched.ui.widget.SwipeRefreshLayout; -import com.google.samples.apps.iosched.util.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -/** - * A base activity that handles common functionality in the app. This includes the - * navigation drawer, login and authentication, Action Bar tweaks, amongst others. - */ -public abstract class BaseActivity extends Activity implements - LoginAndAuthHelper.Callbacks, - SharedPreferences.OnSharedPreferenceChangeListener, - MultiSwipeRefreshLayout.CanChildScrollUpCallback { - private static final String TAG = makeLogTag(BaseActivity.class); - - // the LoginAndAuthHelper handles signing in to Google Play Services and OAuth - private LoginAndAuthHelper mLoginAndAuthHelper; - - // Navigation drawer: - private DrawerLayout mDrawerLayout; - private LPreviewUtilsBase.ActionBarDrawerToggleWrapper mDrawerToggle; - - // allows access to L-Preview APIs through an abstract interface so we can compile with - // both the L Preview SDK and with the API 19 SDK - private LPreviewUtilsBase mLPreviewUtils; - - private ObjectAnimator mStatusBarColorAnimator; - private LinearLayout mAccountListContainer; - private ViewGroup mDrawerItemsListContainer; - private Handler mHandler; - - private ImageView mExpandAccountBoxIndicator; - private boolean mAccountBoxExpanded = false; - - // When set, these components will be shown/hidden in sync with the action bar - // to implement the "quick recall" effect (the Action Bar and the header views disappear - // when you scroll down a list, and reappear quickly when you scroll up). - private ArrayList mHideableHeaderViews = new ArrayList(); - - // Durations for certain animations we use: - private static final int HEADER_HIDE_ANIM_DURATION = 300; - private static final int ACCOUNT_BOX_EXPAND_ANIM_DURATION = 200; - - // symbols for navdrawer items (indices must correspond to array below). This is - // not a list of items that are necessarily *present* in the Nav Drawer; rather, - // it's a list of all possible items. - protected static final int NAVDRAWER_ITEM_MY_SCHEDULE = 0; - protected static final int NAVDRAWER_ITEM_EXPLORE = 1; - protected static final int NAVDRAWER_ITEM_MAP = 2; - protected static final int NAVDRAWER_ITEM_SOCIAL = 3; - protected static final int NAVDRAWER_ITEM_VIDEO_LIBRARY = 4; - protected static final int NAVDRAWER_ITEM_SIGN_IN = 5; - protected static final int NAVDRAWER_ITEM_SETTINGS = 6; - protected static final int NAVDRAWER_ITEM_EXPERTS_DIRECTORY = 7; - protected static final int NAVDRAWER_ITEM_PEOPLE_IVE_MET = 8; - protected static final int NAVDRAWER_ITEM_INVALID = -1; - protected static final int NAVDRAWER_ITEM_SEPARATOR = -2; - protected static final int NAVDRAWER_ITEM_SEPARATOR_SPECIAL = -3; - - // titles for navdrawer items (indices must correspond to the above) - private static final int[] NAVDRAWER_TITLE_RES_ID = new int[]{ - R.string.navdrawer_item_my_schedule, - R.string.navdrawer_item_explore, - R.string.navdrawer_item_map, - R.string.navdrawer_item_social, - R.string.navdrawer_item_video_library, - R.string.navdrawer_item_sign_in, - R.string.navdrawer_item_settings, - R.string.navdrawer_item_experts_directory, - R.string.navdrawer_item_people_ive_met - }; - - // icons for navdrawer items (indices must correspond to above array) - private static final int[] NAVDRAWER_ICON_RES_ID = new int[] { - R.drawable.ic_drawer_my_schedule, // My Schedule - R.drawable.ic_drawer_explore, // Explore - R.drawable.ic_drawer_map, // Map - R.drawable.ic_drawer_social, // Social - R.drawable.ic_drawer_video_library, // Video Library - 0, // Sign in - R.drawable.ic_drawer_settings, - R.drawable.ic_drawer_experts, - R.drawable.ic_drawer_people_met, - }; - - // delay to launch nav drawer item, to allow close animation to play - private static final int NAVDRAWER_LAUNCH_DELAY = 250; - - // fade in and fade out durations for the main content when switching between - // different Activities of the app through the Nav Drawer - private static final int MAIN_CONTENT_FADEOUT_DURATION = 150; - private static final int MAIN_CONTENT_FADEIN_DURATION = 250; - - // list of navdrawer items that were actually added to the navdrawer, in order - private ArrayList mNavDrawerItems = new ArrayList(); - - // views that correspond to each navdrawer item, null if not yet created - private View[] mNavDrawerItemViews = null; - - // SwipeRefreshLayout allows the user to swipe the screen down to trigger a manual refresh - private SwipeRefreshLayout mSwipeRefreshLayout; - - // asynctask that performs GCM registration in the backgorund - private AsyncTask mGCMRegisterTask; - - // handle to our sync observer (that notifies us about changes in our sync state) - private Object mSyncObserverHandle; - - // data bootstrap thread. Data bootstrap is the process of initializing the database - // with the data cache that ships with the app. - Thread mDataBootstrapThread = null; - - // variables that control the Action Bar auto hide behavior (aka "quick recall") - private boolean mActionBarAutoHideEnabled = false; - private int mActionBarAutoHideSensivity = 0; - private int mActionBarAutoHideMinY = 0; - private int mActionBarAutoHideSignal = 0; - private boolean mActionBarShown = true; - - // A Runnable that we should execute when the navigation drawer finishes its closing animation - private Runnable mDeferredOnDrawerClosedRunnable; - - private boolean mManualSyncRequest; - - private int mThemedStatusBarColor; - private int mProgressBarTopWhenActionBarShown; - private static final TypeEvaluator ARGB_EVALUATOR = new ArgbEvaluator(); - private ImageLoader mImageLoader; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - AnalyticsManager.initializeAnalyticsTracker(getApplicationContext()); - - PrefUtils.init(this); - - // Check if the EULA has been accepted; if not, show it. - if (!PrefUtils.isTosAccepted(this)) { - Intent intent = new Intent(this, WelcomeActivity.class); - startActivity(intent); - finish(); - } - - mImageLoader = new ImageLoader(this); - mHandler = new Handler(); - - // Enable or disable each Activity depending on the form factor. This is necessary - // because this app uses many implicit intents where we don't name the exact Activity - // in the Intent, so there should only be one enabled Activity that handles each - // Intent in the app. - UIUtils.enableDisableActivitiesByFormFactor(this); - - if (savedInstanceState == null) { - registerGCMClient(); - } - - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - sp.registerOnSharedPreferenceChangeListener(this); - - ActionBar ab = getActionBar(); - if (ab != null) { - ab.setDisplayHomeAsUpEnabled(true); - } - - mLPreviewUtils = LPreviewUtils.getInstance(this); - mThemedStatusBarColor = getResources().getColor(R.color.theme_primary_dark); - } - - private void trySetupSwipeRefresh() { - mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); - if (mSwipeRefreshLayout != null) { - mSwipeRefreshLayout.setColorScheme( - R.color.refresh_progress_1, - R.color.refresh_progress_2, - R.color.refresh_progress_3, - R.color.refresh_progress_4); - mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - requestDataRefresh(); - } - }); - - if (mSwipeRefreshLayout instanceof MultiSwipeRefreshLayout) { - MultiSwipeRefreshLayout mswrl = (MultiSwipeRefreshLayout) mSwipeRefreshLayout; - mswrl.setCanChildScrollUpCallback(this); - } - } - } - - protected void setProgressBarTopWhenActionBarShown(int progressBarTopWhenActionBarShown) { - mProgressBarTopWhenActionBarShown = progressBarTopWhenActionBarShown; - updateSwipeRefreshProgressBarTop(); - } - - private void updateSwipeRefreshProgressBarTop() { - if (mSwipeRefreshLayout == null) { - return; - } - - if (mActionBarShown) { - mSwipeRefreshLayout.setProgressBarTop(mProgressBarTopWhenActionBarShown); - } else { - mSwipeRefreshLayout.setProgressBarTop(0); - } - } - - /** - * Returns the navigation drawer item that corresponds to this Activity. Subclasses - * of BaseActivity override this to indicate what nav drawer item corresponds to them - * Return NAVDRAWER_ITEM_INVALID to mean that this Activity should not have a Nav Drawer. - */ - protected int getSelfNavDrawerItem() { - return NAVDRAWER_ITEM_INVALID; - } - - /** - * Sets up the navigation drawer as appropriate. Note that the nav drawer will be - * different depending on whether the attendee indicated that they are attending the - * event on-site vs. attending remotely. - */ - private void setupNavDrawer() { - // What nav drawer item should be selected? - int selfItem = getSelfNavDrawerItem(); - - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - if (mDrawerLayout == null) { - return; - } - if (selfItem == NAVDRAWER_ITEM_INVALID) { - // do not show a nav drawer - View navDrawer = mDrawerLayout.findViewById(R.id.navdrawer); - if (navDrawer != null) { - ((ViewGroup) navDrawer.getParent()).removeView(navDrawer); - } - mDrawerLayout = null; - return; - } - - mDrawerToggle = mLPreviewUtils.setupDrawerToggle(mDrawerLayout, new DrawerLayout.DrawerListener() { - @Override - public void onDrawerClosed(View drawerView) { - // run deferred action, if we have one - if (mDeferredOnDrawerClosedRunnable != null) { - mDeferredOnDrawerClosedRunnable.run(); - mDeferredOnDrawerClosedRunnable = null; - } - if (mAccountBoxExpanded) { - mAccountBoxExpanded = false; - setupAccountBoxToggle(); - } - invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() - updateStatusBarForNavDrawerSlide(0f); - onNavDrawerStateChanged(false, false); - } - - @Override - public void onDrawerOpened(View drawerView) { - invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() - updateStatusBarForNavDrawerSlide(1f); - onNavDrawerStateChanged(true, false); - } - - @Override - public void onDrawerStateChanged(int newState) { - invalidateOptionsMenu(); - onNavDrawerStateChanged(isNavDrawerOpen(), newState != DrawerLayout.STATE_IDLE); - } - - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - updateStatusBarForNavDrawerSlide(slideOffset); - onNavDrawerSlide(slideOffset); - } - }); - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); - - getActionBar().setDisplayHomeAsUpEnabled(true); - getActionBar().setHomeButtonEnabled(true); - - // populate the nav drawer with the correct items - populateNavDrawer(); - - mDrawerToggle.syncState(); - - // When the user runs the app for the first time, we want to land them with the - // navigation drawer open. But just the first time. - if (!PrefUtils.isWelcomeDone(this)) { - // first run of the app starts with the nav drawer open - PrefUtils.markWelcomeDone(this); - mDrawerLayout.openDrawer(Gravity.START); - } - } - - // Subclasses can override this for custom behavior - protected void onNavDrawerStateChanged(boolean isOpen, boolean isAnimating) { - if (mActionBarAutoHideEnabled && isOpen) { - autoShowOrHideActionBar(true); - } - } - - protected void onNavDrawerSlide(float offset) {} - - protected boolean isNavDrawerOpen() { - return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.START); - } - - /** Populates the navigation drawer with the appropriate items. */ - private void populateNavDrawer() { - boolean attendeeAtVenue = PrefUtils.isAttendeeAtVenue(this); - mNavDrawerItems.clear(); - - // decide which items will appear in the nav drawer - if (AccountUtils.hasActiveAccount(this)) { - // Only logged-in users can save sessions, so if there is no active account, - // there is no My Schedule - mNavDrawerItems.add(NAVDRAWER_ITEM_MY_SCHEDULE); - } else { - // If no active account, show Sign In - mNavDrawerItems.add(NAVDRAWER_ITEM_SIGN_IN); - } - - // Explore is always shown - mNavDrawerItems.add(NAVDRAWER_ITEM_EXPLORE); - - // If the attendee is on-site, show Map on the nav drawer - if (attendeeAtVenue) { - mNavDrawerItems.add(NAVDRAWER_ITEM_MAP); - } - mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR); - - // If attendee is on-site, show the People I've Met item - if (attendeeAtVenue) { - mNavDrawerItems.add(NAVDRAWER_ITEM_PEOPLE_IVE_MET); - } - - // If the experts directory hasn't expired, show it - if (!Config.hasExpertsDirectoryExpired()) { - mNavDrawerItems.add(NAVDRAWER_ITEM_EXPERTS_DIRECTORY); - } - - // Other items that are always in the nav drawer irrespective of whether the - // attendee is on-site or remote: - mNavDrawerItems.add(NAVDRAWER_ITEM_SOCIAL); - mNavDrawerItems.add(NAVDRAWER_ITEM_VIDEO_LIBRARY); - mNavDrawerItems.add(NAVDRAWER_ITEM_SEPARATOR_SPECIAL); - mNavDrawerItems.add(NAVDRAWER_ITEM_SETTINGS); - - createNavDrawerItems(); - } - - private void createNavDrawerItems() { - mDrawerItemsListContainer = (ViewGroup) findViewById(R.id.navdrawer_items_list); - if (mDrawerItemsListContainer == null) { - return; - } - - mNavDrawerItemViews = new View[mNavDrawerItems.size()]; - mDrawerItemsListContainer.removeAllViews(); - int i = 0; - for (int itemId : mNavDrawerItems) { - mNavDrawerItemViews[i] = makeNavDrawerItem(itemId, mDrawerItemsListContainer); - mDrawerItemsListContainer.addView(mNavDrawerItemViews[i]); - ++i; - } - } - - /** - * Sets up the given navdrawer item's appearance to the selected state. Note: this could - * also be accomplished (perhaps more cleanly) with state-based layouts. - */ - private void setSelectedNavDrawerItem(int itemId) { - if (mNavDrawerItemViews != null) { - for (int i = 0; i < mNavDrawerItemViews.length; i++) { - if (i < mNavDrawerItems.size()) { - int thisItemId = mNavDrawerItems.get(i); - formatNavDrawerItem(mNavDrawerItemViews[i], thisItemId, itemId == thisItemId); - } - } - } - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(PrefUtils.PREF_ATTENDEE_AT_VENUE)) { - LOGD(TAG, "Attendee at venue preference changed, repopulating nav drawer and menu."); - populateNavDrawer(); - invalidateOptionsMenu(); - } - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - setupNavDrawer(); - setupAccountBox(); - - trySetupSwipeRefresh(); - updateSwipeRefreshProgressBarTop(); - - View mainContent = findViewById(R.id.main_content); - if (mainContent != null) { - mainContent.setAlpha(0); - mainContent.animate().alpha(1).setDuration(MAIN_CONTENT_FADEIN_DURATION); - } else { - LOGW(TAG, "No view with ID main_content to fade in."); - } - } - - /** - * Sets up the account box. The account box is the area at the top of the nav drawer that - * shows which account the user is logged in as, and lets them switch accounts. It also - * shows the user's Google+ cover photo as background. - */ - private void setupAccountBox() { - mAccountListContainer = (LinearLayout) findViewById(R.id.account_list); - - if (mAccountListContainer == null) { - //This activity does not have an account box - return; - } - - final View chosenAccountView = findViewById(R.id.chosen_account_view); - Account chosenAccount = AccountUtils.getActiveAccount(this); - if (chosenAccount == null) { - // No account logged in; hide account box - chosenAccountView.setVisibility(View.GONE); - mAccountListContainer.setVisibility(View.GONE); - return; - } else { - chosenAccountView.setVisibility(View.VISIBLE); - mAccountListContainer.setVisibility(View.INVISIBLE); - } - - AccountManager am = AccountManager.get(this); - Account[] accountArray = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); - List accounts = new ArrayList(Arrays.asList(accountArray)); - accounts.remove(chosenAccount); - - ImageView coverImageView = (ImageView) chosenAccountView.findViewById(R.id.profile_cover_image); - ImageView profileImageView = (ImageView) chosenAccountView.findViewById(R.id.profile_image); - TextView nameTextView = (TextView) chosenAccountView.findViewById(R.id.profile_name_text); - TextView email = (TextView) chosenAccountView.findViewById(R.id.profile_email_text); - mExpandAccountBoxIndicator = (ImageView) findViewById(R.id.expand_account_box_indicator); - - String name = AccountUtils.getPlusName(this); - if (name == null) { - nameTextView.setVisibility(View.GONE); - } else { - nameTextView.setText(name); - } - - String imageUrl = AccountUtils.getPlusImageUrl(this); - if (imageUrl != null) { - mImageLoader.loadImage(imageUrl, profileImageView); - } - - String coverImageUrl = AccountUtils.getPlusCoverUrl(this); - if (coverImageUrl != null) { - mImageLoader.loadImage(coverImageUrl, coverImageView); - } else { - coverImageView.setImageResource(R.drawable.default_cover); - } - - email.setText(chosenAccount.name); - - if (accounts.isEmpty()) { - // There's only one account on the device, so no need for a switcher. - mExpandAccountBoxIndicator.setVisibility(View.GONE); - mAccountListContainer.setVisibility(View.GONE); - chosenAccountView.setEnabled(false); - return; - } - - chosenAccountView.setEnabled(true); - - mExpandAccountBoxIndicator.setVisibility(View.VISIBLE); - chosenAccountView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mAccountBoxExpanded = !mAccountBoxExpanded; - setupAccountBoxToggle(); - } - }); - setupAccountBoxToggle(); - - populateAccountList(accounts); - } - - private void populateAccountList(List accounts) { - mAccountListContainer.removeAllViews(); - - LayoutInflater layoutInflater = LayoutInflater.from(this); - for (Account account : accounts) { - View itemView = layoutInflater.inflate(R.layout.list_item_account, - mAccountListContainer, false); - ((TextView) itemView.findViewById(R.id.profile_email_text)) - .setText(account.name); - final String accountName = account.name; - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ConnectivityManager cm = (ConnectivityManager) - getSystemService(CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - if (activeNetwork == null || !activeNetwork.isConnected()) { - // if there's no network, don't try to change the selected account - Toast.makeText(BaseActivity.this, R.string.no_connection_cant_login, - Toast.LENGTH_SHORT).show(); - mDrawerLayout.closeDrawer(Gravity.START); - return; - } else { - LOGD(TAG, "User requested switch to account: " + accountName); - AccountUtils.setActiveAccount(BaseActivity.this, accountName); - onAccountChangeRequested(); - startLoginProcess(); - mAccountBoxExpanded = false; - setupAccountBoxToggle(); - mDrawerLayout.closeDrawer(Gravity.START); - setupAccountBox(); - } - } - }); - mAccountListContainer.addView(itemView); - } - } - - protected void onAccountChangeRequested() { - // override if you want to be notified when another account has been selected account has changed - } - - private void setupAccountBoxToggle() { - int selfItem = getSelfNavDrawerItem(); - if (mDrawerLayout == null || selfItem == NAVDRAWER_ITEM_INVALID) { - // this Activity does not have a nav drawer - return; - } - mExpandAccountBoxIndicator.setImageResource(mAccountBoxExpanded - ? R.drawable.ic_drawer_accounts_collapse - : R.drawable.ic_drawer_accounts_expand); - int hideTranslateY = -mAccountListContainer.getHeight() / 4; // last 25% of animation - if (mAccountBoxExpanded && mAccountListContainer.getTranslationY() == 0) { - // initial setup - mAccountListContainer.setAlpha(0); - mAccountListContainer.setTranslationY(hideTranslateY); - } - - AnimatorSet set = new AnimatorSet(); - set.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mDrawerItemsListContainer.setVisibility(mAccountBoxExpanded - ? View.INVISIBLE : View.VISIBLE); - mAccountListContainer.setVisibility(mAccountBoxExpanded - ? View.VISIBLE : View.INVISIBLE); - } - - @Override - public void onAnimationCancel(Animator animation) { - onAnimationEnd(animation); - } - }); - - if (mAccountBoxExpanded) { - mAccountListContainer.setVisibility(View.VISIBLE); - AnimatorSet subSet = new AnimatorSet(); - subSet.playTogether( - ObjectAnimator.ofFloat(mAccountListContainer, View.ALPHA, 1) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), - ObjectAnimator.ofFloat(mAccountListContainer, View.TRANSLATION_Y, 0) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); - set.playSequentially( - ObjectAnimator.ofFloat(mDrawerItemsListContainer, View.ALPHA, 0) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), - subSet); - set.start(); - } else { - mDrawerItemsListContainer.setVisibility(View.VISIBLE); - AnimatorSet subSet = new AnimatorSet(); - subSet.playTogether( - ObjectAnimator.ofFloat(mAccountListContainer, View.ALPHA, 0) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION), - ObjectAnimator.ofFloat(mAccountListContainer, View.TRANSLATION_Y, - hideTranslateY) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); - set.playSequentially( - subSet, - ObjectAnimator.ofFloat(mDrawerItemsListContainer, View.ALPHA, 1) - .setDuration(ACCOUNT_BOX_EXPAND_ANIM_DURATION)); - set.start(); - } - - set.start(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (mDrawerToggle != null) { - mDrawerToggle.onConfigurationChanged(newConfig); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) { - return true; - } - - switch (id) { - case R.id.menu_about: - HelpUtils.showAbout(this); - return true; - - case R.id.menu_wifi: - WiFiUtils.showWiFiDialog(this); - return true; - - case R.id.menu_i_o_hunt: - launchIoHunt(); - return true; - - case R.id.menu_debug: - if (BuildConfig.DEBUG) { - startActivity(new Intent(this, DebugActionRunnerActivity.class)); - } - return true; - - case R.id.menu_refresh: - requestDataRefresh(); - break; - - case R.id.menu_io_extended: - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse(Config.IO_EXTENDED_LINK))); - break; - - case R.id.menu_map: - startActivity(new Intent(this, UIUtils.getMapActivityClass(this))); - finish(); - break; - } - return super.onOptionsItemSelected(item); - } - - private void launchIoHunt() { - if (!TextUtils.isEmpty(Config.IO_HUNT_PACKAGE_NAME)) { - LOGD(TAG, "Attempting to launch I/O hunt."); - PackageManager pm = getPackageManager(); - Intent launchIntent = pm.getLaunchIntentForPackage(Config.IO_HUNT_PACKAGE_NAME); - if (launchIntent != null) { - // start I/O Hunt - LOGD(TAG, "I/O hunt intent found, launching."); - startActivity(launchIntent); - } else { - // send user to the Play Store to download it - LOGD(TAG, "I/O hunt intent NOT found, going to Play Store."); - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse( - Config.PLAY_STORE_URL_PREFIX + Config.IO_HUNT_PACKAGE_NAME)); - startActivity(intent); - } - } - } - - protected void requestDataRefresh() { - Account activeAccount = AccountUtils.getActiveAccount(this); - ContentResolver contentResolver = getContentResolver(); - if (contentResolver.isSyncActive(activeAccount, ScheduleContract.CONTENT_AUTHORITY)) { - LOGD(TAG, "Ignoring manual sync request because a sync is already in progress."); - return; - } - mManualSyncRequest = true; - LOGD(TAG, "Requesting manual data refresh."); - SyncHelper.requestManualSync(activeAccount); - } - - private void goToNavDrawerItem(int item) { - Intent intent; - switch (item) { - case NAVDRAWER_ITEM_MY_SCHEDULE: - intent = new Intent(this, MyScheduleActivity.class); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_EXPLORE: - intent = new Intent(this, BrowseSessionsActivity.class); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_MAP: - intent = new Intent(this, UIUtils.getMapActivityClass(this)); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_SOCIAL: - intent = new Intent(this, SocialActivity.class); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_EXPERTS_DIRECTORY: - intent = new Intent(this, ExpertsDirectoryActivity.class); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_PEOPLE_IVE_MET: - intent = new Intent(this, PeopleIveMetActivity.class); - startActivity(intent); - finish(); - break; - case NAVDRAWER_ITEM_SIGN_IN: - signInOrCreateAnAccount(); - break; - case NAVDRAWER_ITEM_SETTINGS: - intent = new Intent(this, SettingsActivity.class); - startActivity(intent); - break; - case NAVDRAWER_ITEM_VIDEO_LIBRARY: - intent = new Intent(this, VideoLibraryActivity.class); - startActivity(intent); - finish(); - break; - } - } - - private void signInOrCreateAnAccount() { - //Get list of accounts on device. - AccountManager am = AccountManager.get(BaseActivity.this); - Account[] accountArray = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); - if (accountArray.length == 0) { - //Send the user to the "Add Account" page. - Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); - intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[] {"com.google"}); - startActivity(intent); - } else { - //Try to log the user in with the first account on the device. - startLoginProcess(); - mDrawerLayout.closeDrawer(Gravity.START); - } - } - - private void onNavDrawerItemClicked(final int itemId) { - if (itemId == getSelfNavDrawerItem()) { - mDrawerLayout.closeDrawer(Gravity.START); - return; - } - - if (isSpecialItem(itemId)) { - goToNavDrawerItem(itemId); - } else { - // launch the target Activity after a short delay, to allow the close animation to play - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - goToNavDrawerItem(itemId); - } - }, NAVDRAWER_LAUNCH_DELAY); - - // change the active item on the list so the user can see the item changed - setSelectedNavDrawerItem(itemId); - // fade out the main content - View mainContent = findViewById(R.id.main_content); - if (mainContent != null) { - mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION); - } - } - - mDrawerLayout.closeDrawer(Gravity.START); - } - - protected void configureStandardMenuItems(Menu menu) { - MenuItem wifiItem = menu.findItem(R.id.menu_wifi); - if (wifiItem != null && !WiFiUtils.shouldOfferToSetupWifi(this, false)) { - wifiItem.setVisible(false); - } - - MenuItem debugItem = menu.findItem(R.id.menu_debug); - if (debugItem != null) { - debugItem.setVisible(BuildConfig.DEBUG); - } - - MenuItem ioExtendedItem = menu.findItem(R.id.menu_io_extended); - if (ioExtendedItem != null) { - ioExtendedItem.setVisible(PrefUtils.shouldOfferIOExtended(this, false)); - } - - // if attendee is remote, show map on the overflow instead of on the nav bar - final boolean isRemote = !PrefUtils.isAttendeeAtVenue(this); - final MenuItem mapItem = menu.findItem(R.id.menu_map); - if (mapItem != null) { - mapItem.setVisible(isRemote); - } - - MenuItem ioHuntItem = menu.findItem(R.id.menu_i_o_hunt); - if (ioHuntItem != null) { - ioHuntItem.setVisible(!isRemote && !TextUtils.isEmpty(Config.IO_HUNT_PACKAGE_NAME)); - } - } - - @Override - protected void onResume() { - super.onResume(); - - // Verifies the proper version of Google Play Services exists on the device. - PlayServicesUtils.checkGooglePlaySevices(this); - - // Watch for sync state changes - mSyncStatusObserver.onStatusChanged(0); - final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING | - ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE; - mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask, mSyncStatusObserver); - } - - @Override - protected void onPause() { - super.onPause(); - if (mSyncObserverHandle != null) { - ContentResolver.removeStatusChangeListener(mSyncObserverHandle); - mSyncObserverHandle = null; - } - } - - /** - * Converts an intent into a {@link Bundle} suitable for use as fragment arguments. - */ - public static Bundle intentToFragmentArguments(Intent intent) { - Bundle arguments = new Bundle(); - if (intent == null) { - return arguments; - } - - final Uri data = intent.getData(); - if (data != null) { - arguments.putParcelable("_uri", data); - } - - final Bundle extras = intent.getExtras(); - if (extras != null) { - arguments.putAll(intent.getExtras()); - } - - return arguments; - } - - /** - * Converts a fragment arguments bundle into an intent. - */ - public static Intent fragmentArgumentsToIntent(Bundle arguments) { - Intent intent = new Intent(); - if (arguments == null) { - return intent; - } - - final Uri data = arguments.getParcelable("_uri"); - if (data != null) { - intent.setData(data); - } - - intent.putExtras(arguments); - intent.removeExtra("_uri"); - return intent; - } - - @Override - public void onStart() { - LOGD(TAG, "onStart"); - super.onStart(); - - // Perform one-time bootstrap setup, if needed - if (!PrefUtils.isDataBootstrapDone(this) && mDataBootstrapThread == null) { - LOGD(TAG, "One-time data bootstrap not done yet. Doing now."); - performDataBootstrap(); - } - - startLoginProcess(); - } - - /** - * Performs the one-time data bootstrap. This means taking our prepackaged conference data - * from the R.raw.bootstrap_data resource, and parsing it to populate the database. This - * data contains the sessions, speakers, etc. - */ - private void performDataBootstrap() { - final Context appContext = getApplicationContext(); - LOGD(TAG, "Starting data bootstrap background thread."); - mDataBootstrapThread = new Thread(new Runnable() { - @Override - public void run() { - LOGD(TAG, "Starting data bootstrap process."); - try { - // Load data from bootstrap raw resource - String bootstrapJson = JSONHandler.parseResource(appContext, R.raw.bootstrap_data); - - // Apply the data we read to the database with the help of the ConferenceDataHandler - ConferenceDataHandler dataHandler = new ConferenceDataHandler(appContext); - dataHandler.applyConferenceData(new String[]{bootstrapJson}, - Config.BOOTSTRAP_DATA_TIMESTAMP, false); - SyncHelper.performPostSyncChores(appContext); - LOGI(TAG, "End of bootstrap -- successful. Marking boostrap as done."); - PrefUtils.markSyncSucceededNow(appContext); - PrefUtils.markDataBootstrapDone(appContext); - getContentResolver().notifyChange(Uri.parse(ScheduleContract.CONTENT_AUTHORITY), - null, false); - } catch (IOException ex) { - // This is serious -- if this happens, the app won't work :-( - // This is unlikely to happen in production, but IF it does, we apply - // this workaround as a fallback: we pretend we managed to do the bootstrap - // and hope that a remote sync will work. - LOGE(TAG, "*** ERROR DURING BOOTSTRAP! Problem in bootstrap data?"); - LOGE(TAG, "Applying fallback -- marking boostrap as done; sync might fix problem."); - PrefUtils.markDataBootstrapDone(appContext); - } - - mDataBootstrapThread = null; - - // Request a manual sync immediately after the bootstrapping process, in case we - // have an active connection. Otherwise, the scheduled sync could take a while. - SyncHelper.requestManualSync(AccountUtils.getActiveAccount(appContext)); - } - }); - mDataBootstrapThread.start(); - } - - /** - * Returns the default account on the device. We use the rule that the first account - * should be the default. It's arbitrary, but the alternative would be showing an account - * chooser popup which wouldn't be a smooth first experience with the app. Since the user - * can easily switch the account with the nav drawer, we opted for this implementation. - */ - private String getDefaultAccount() { - // Choose first account on device. - LOGD(TAG, "Choosing default account (first account on device)"); - AccountManager am = AccountManager.get(this); - Account[] accounts = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); - if (accounts.length == 0) { - // No Google accounts on device. - LOGW(TAG, "No Google accounts on device; not setting default account."); - return null; - } - - LOGD(TAG, "Default account is: " + accounts[0].name); - return accounts[0].name; - } - - - private void complainMustHaveGoogleAccount() { - LOGD(TAG, "Complaining about missing Google account."); - new AlertDialog.Builder(this) - .setTitle(R.string.google_account_required_title) - .setMessage(R.string.google_account_required_message) - .setPositiveButton(R.string.add_account, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - promptAddAccount(); - } - }) - .setNegativeButton(R.string.not_now, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }) - .show(); - } - - private void promptAddAccount() { - Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT); - intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[]{"com.google"}); - startActivity(intent); - finish(); - } - - private void startLoginProcess() { - LOGD(TAG, "Starting login process."); - if (!AccountUtils.hasActiveAccount(this)) { - LOGD(TAG, "No active account, attempting to pick a default."); - String defaultAccount = getDefaultAccount(); - if (defaultAccount == null) { - LOGE(TAG, "Failed to pick default account (no accounts). Failing."); - complainMustHaveGoogleAccount(); - return; - } - LOGD(TAG, "Default to: " + defaultAccount); - AccountUtils.setActiveAccount(this, defaultAccount); - } - - if (!AccountUtils.hasActiveAccount(this)) { - LOGD(TAG, "Can't proceed with login -- no account chosen."); - return; - } else { - LOGD(TAG, "Chosen account: " + AccountUtils.getActiveAccountName(this)); - } - - String accountName = AccountUtils.getActiveAccountName(this); - LOGD(TAG, "Chosen account: " + AccountUtils.getActiveAccountName(this)); - - if (mLoginAndAuthHelper != null && mLoginAndAuthHelper.getAccountName().equals(accountName)) { - LOGD(TAG, "Helper already set up; simply starting it."); - mLoginAndAuthHelper.start(); - return; - } - - LOGD(TAG, "Starting login process with account " + accountName); - - if (mLoginAndAuthHelper != null) { - LOGD(TAG, "Tearing down old Helper, was " + mLoginAndAuthHelper.getAccountName()); - if (mLoginAndAuthHelper.isStarted()) { - LOGD(TAG, "Stopping old Helper"); - mLoginAndAuthHelper.stop(); - } - mLoginAndAuthHelper = null; - } - - LOGD(TAG, "Creating and starting new Helper with account: " + accountName); - mLoginAndAuthHelper = new LoginAndAuthHelper(this, this, accountName); - mLoginAndAuthHelper.start(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mLoginAndAuthHelper == null || !mLoginAndAuthHelper.onActivityResult(requestCode, - resultCode, data)) { - super.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - public void onStop() { - LOGD(TAG, "onStop"); - super.onStop(); - if (mLoginAndAuthHelper != null) { - mLoginAndAuthHelper.stop(); - } - } - - @Override - public void onPlusInfoLoaded(String accountName) { - setupAccountBox(); - populateNavDrawer(); - } - - /** - * Called when authentication succeeds. This may either happen because the user just - * authenticated for the first time (and went through the sign in flow), or because it's - * a returning user. - * @param accountName name of the account that just authenticated successfully. - * @param newlyAuthenticated If true, this user just authenticated for the first time. - * If false, it's a returning user. - */ - @Override - public void onAuthSuccess(String accountName, boolean newlyAuthenticated) { - Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); - LOGD(TAG, "onAuthSuccess, account " + accountName + ", newlyAuthenticated=" + newlyAuthenticated); - - refreshAccountDependantData(); - - if (newlyAuthenticated) { - LOGD(TAG, "Enabling auto sync on content provider for account " + accountName); - SyncHelper.updateSyncInterval(this, account); - SyncHelper.requestManualSync(account); - } - - setupAccountBox(); - populateNavDrawer(); - registerGCMClient(); - } - - @Override - public void onAuthFailure(String accountName) { - LOGD(TAG, "Auth failed for account " + accountName); - refreshAccountDependantData(); - } - - protected void refreshAccountDependantData() { - // Force local data refresh for data that depends on the logged user: - LOGD(TAG, "Refreshing MySchedule data"); - getContentResolver().notifyChange(ScheduleContract.MySchedule.CONTENT_URI, null, false); - } - - protected void retryAuth() { - mLoginAndAuthHelper.retryAuthByUserRequest(); - } - - /** - * Initializes the Action Bar auto-hide (aka Quick Recall) effect. - */ - private void initActionBarAutoHide() { - mActionBarAutoHideEnabled = true; - mActionBarAutoHideMinY = getResources().getDimensionPixelSize( - R.dimen.action_bar_auto_hide_min_y); - mActionBarAutoHideSensivity = getResources().getDimensionPixelSize( - R.dimen.action_bar_auto_hide_sensivity); - } - - /** - * Indicates that the main content has scrolled (for the purposes of showing/hiding - * the action bar for the "action bar auto hide" effect). currentY and deltaY may be exact - * (if the underlying view supports it) or may be approximate indications: - * deltaY may be INT_MAX to mean "scrolled forward indeterminately" and INT_MIN to mean - * "scrolled backward indeterminately". currentY may be 0 to mean "somewhere close to the - * start of the list" and INT_MAX to mean "we don't know, but not at the start of the list" - */ - private void onMainContentScrolled(int currentY, int deltaY) { - if (deltaY > mActionBarAutoHideSensivity) { - deltaY = mActionBarAutoHideSensivity; - } else if (deltaY < -mActionBarAutoHideSensivity) { - deltaY = -mActionBarAutoHideSensivity; - } - - if (Math.signum(deltaY) * Math.signum(mActionBarAutoHideSignal) < 0) { - // deltaY is a motion opposite to the accumulated signal, so reset signal - mActionBarAutoHideSignal = deltaY; - } else { - // add to accumulated signal - mActionBarAutoHideSignal += deltaY; - } - - boolean shouldShow = currentY < mActionBarAutoHideMinY || - (mActionBarAutoHideSignal <= -mActionBarAutoHideSensivity); - autoShowOrHideActionBar(shouldShow); - } - - protected void autoShowOrHideActionBar(boolean show) { - if (show == mActionBarShown) { - return; - } - - mActionBarShown = show; - getLPreviewUtils().showHideActionBarIfPartOfDecor(show); - onActionBarAutoShowOrHide(show); - } - - protected void enableActionBarAutoHide(final ListView listView) { - initActionBarAutoHide(); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - final static int ITEMS_THRESHOLD = 3; - int lastFvi = 0; - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - onMainContentScrolled(firstVisibleItem <= ITEMS_THRESHOLD ? 0 : Integer.MAX_VALUE, - lastFvi - firstVisibleItem > 0 ? Integer.MIN_VALUE : - lastFvi == firstVisibleItem ? 0 : Integer.MAX_VALUE - ); - lastFvi = firstVisibleItem; - } - }); - } - - private View makeNavDrawerItem(final int itemId, ViewGroup container) { - boolean selected = getSelfNavDrawerItem() == itemId; - int layoutToInflate = 0; - if (itemId == NAVDRAWER_ITEM_SEPARATOR) { - layoutToInflate = R.layout.navdrawer_separator; - } else if (itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL) { - layoutToInflate = R.layout.navdrawer_separator; - } else { - layoutToInflate = R.layout.navdrawer_item; - } - View view = getLayoutInflater().inflate(layoutToInflate, container, false); - - if (isSeparator(itemId)) { - // we are done - UIUtils.setAccessibilityIgnore(view); - return view; - } - - ImageView iconView = (ImageView) view.findViewById(R.id.icon); - TextView titleView = (TextView) view.findViewById(R.id.title); - int iconId = itemId >= 0 && itemId < NAVDRAWER_ICON_RES_ID.length ? - NAVDRAWER_ICON_RES_ID[itemId] : 0; - int titleId = itemId >= 0 && itemId < NAVDRAWER_TITLE_RES_ID.length ? - NAVDRAWER_TITLE_RES_ID[itemId] : 0; - - // set icon and text - iconView.setVisibility(iconId > 0 ? View.VISIBLE : View.GONE); - if (iconId > 0) { - iconView.setImageResource(iconId); - } - titleView.setText(getString(titleId)); - - formatNavDrawerItem(view, itemId, selected); - - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onNavDrawerItemClicked(itemId); - } - }); - - return view; - } - - private boolean isSpecialItem(int itemId) { - return itemId == NAVDRAWER_ITEM_SETTINGS; - } - - private boolean isSeparator(int itemId) { - return itemId == NAVDRAWER_ITEM_SEPARATOR || itemId == NAVDRAWER_ITEM_SEPARATOR_SPECIAL; - } - - private void formatNavDrawerItem(View view, int itemId, boolean selected) { - if (isSeparator(itemId)) { - // not applicable - return; - } - - ImageView iconView = (ImageView) view.findViewById(R.id.icon); - TextView titleView = (TextView) view.findViewById(R.id.title); - - // configure its appearance according to whether or not it's selected - titleView.setTextColor(selected ? - getResources().getColor(R.color.navdrawer_text_color_selected) : - getResources().getColor(R.color.navdrawer_text_color)); - iconView.setColorFilter(selected ? - getResources().getColor(R.color.navdrawer_icon_tint_selected) : - getResources().getColor(R.color.navdrawer_icon_tint)); - } - - /** Registers device on the GCM server, if necessary. */ - private void registerGCMClient() { - GCMRegistrar.checkDevice(this); - GCMRegistrar.checkManifest(this); - - final String regId = GCMRegistrar.getRegistrationId(this); - - if (TextUtils.isEmpty(regId)) { - // Automatically registers application on startup. - GCMRegistrar.register(this, Config.GCM_SENDER_ID); - - } else { - // Get the correct GCM key for the user. GCM key is a somewhat non-standard - // approach we use in this app. For more about this, check GCM.TXT. - final String gcmKey = AccountUtils.hasActiveAccount(this) ? - AccountUtils.getGcmKey(this, AccountUtils.getActiveAccountName(this)) : null; - // Device is already registered on GCM, needs to check if it is - // registered on our server as well. - if (ServerUtilities.isRegisteredOnServer(this, gcmKey)) { - // Skips registration. - LOGI(TAG, "Already registered on the GCM server with right GCM key."); - } else { - // Try to register again, but not in the UI thread. - // It's also necessary to cancel the thread onDestroy(), - // hence the use of AsyncTask instead of a raw thread. - mGCMRegisterTask = new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - LOGI(TAG, "Registering on the GCM server with GCM key: " - + AccountUtils.sanitizeGcmKey(gcmKey)); - boolean registered = ServerUtilities.register(BaseActivity.this, - regId, gcmKey); - // At this point all attempts to register with the app - // server failed, so we need to unregister the device - // from GCM - the app will try to register again when - // it is restarted. Note that GCM will send an - // unregistered callback upon completion, but - // GCMIntentService.onUnregistered() will ignore it. - if (!registered) { - LOGI(TAG, "GCM registration failed."); - GCMRegistrar.unregister(BaseActivity.this); - } else { - LOGI(TAG, "GCM registration successful."); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - mGCMRegisterTask = null; - } - }; - mGCMRegisterTask.execute(null, null, null); - } - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - if (mGCMRegisterTask != null) { - LOGD(TAG, "Cancelling GCM registration task."); - mGCMRegisterTask.cancel(true); - } - - try { - GCMRegistrar.onDestroy(this); - } catch (Exception e) { - LOGW(TAG, "C2DM unregistration error", e); - } - - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - sp.unregisterOnSharedPreferenceChangeListener(this); - } - - private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() { - @Override - public void onStatusChanged(int which) { - runOnUiThread(new Runnable() { - @Override - public void run() { - String accountName = AccountUtils.getActiveAccountName(BaseActivity.this); - if (TextUtils.isEmpty(accountName)) { - onRefreshingStateChanged(false); - mManualSyncRequest = false; - return; - } - - Account account = new Account(accountName, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); - boolean syncActive = ContentResolver.isSyncActive( - account, ScheduleContract.CONTENT_AUTHORITY); - boolean syncPending = ContentResolver.isSyncPending( - account, ScheduleContract.CONTENT_AUTHORITY); - if (!syncActive && !syncPending) { - mManualSyncRequest = false; - } - onRefreshingStateChanged(syncActive || (mManualSyncRequest && syncPending)); - } - }); - } - }; - - protected void onRefreshingStateChanged(boolean refreshing) { - if (mSwipeRefreshLayout != null) { - mSwipeRefreshLayout.setRefreshing(refreshing); - } - } - - protected void enableDisableSwipeRefresh(boolean enable) { - if (mSwipeRefreshLayout != null) { - mSwipeRefreshLayout.setEnabled(enable); - } - } - - protected void registerHideableHeaderView(View hideableHeaderView) { - if (!mHideableHeaderViews.contains(hideableHeaderView)) { - mHideableHeaderViews.add(hideableHeaderView); - } - } - - protected void deregisterHideableHeaderView(View hideableHeaderView) { - if (mHideableHeaderViews.contains(hideableHeaderView)) { - mHideableHeaderViews.remove(hideableHeaderView); - } - } - - public LPreviewUtilsBase getLPreviewUtils() { - return mLPreviewUtils; - } - - private void updateStatusBarForNavDrawerSlide(float slideOffset) { - if (mStatusBarColorAnimator != null) { - mStatusBarColorAnimator.cancel(); - } - - if (!mActionBarShown) { - mLPreviewUtils.setStatusBarColor(Color.BLACK); - return; - } - - mLPreviewUtils.setStatusBarColor((Integer) ARGB_EVALUATOR.evaluate(slideOffset, - mThemedStatusBarColor, Color.BLACK)); - } - - protected void onActionBarAutoShowOrHide(boolean shown) { - if (mStatusBarColorAnimator != null) { - mStatusBarColorAnimator.cancel(); - } - mStatusBarColorAnimator = ObjectAnimator.ofInt(mLPreviewUtils, "statusBarColor", - shown ? mThemedStatusBarColor : Color.BLACK).setDuration(250); - mStatusBarColorAnimator.setEvaluator(ARGB_EVALUATOR); - mStatusBarColorAnimator.start(); - - updateSwipeRefreshProgressBarTop(); - - for (View view : mHideableHeaderViews) { - if (shown) { - view.animate() - .translationY(0) - .alpha(1) - .setDuration(HEADER_HIDE_ANIM_DURATION) - .setInterpolator(new DecelerateInterpolator()); - } else { - view.animate() - .translationY(-view.getBottom()) - .alpha(0) - .setDuration(HEADER_HIDE_ANIM_DURATION) - .setInterpolator(new DecelerateInterpolator()); - } - } - } - - @Override - public boolean canSwipeRefreshChildScrollUp() { - return false; - } -} diff --git a/android/src/main/java/com/google/samples/apps/iosched/ui/BrowseSessionsActivity.java b/android/src/main/java/com/google/samples/apps/iosched/ui/BrowseSessionsActivity.java deleted file mode 100644 index adb76477cf..0000000000 --- a/android/src/main/java/com/google/samples/apps/iosched/ui/BrowseSessionsActivity.java +++ /dev/null @@ -1,713 +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 com.google.samples.apps.iosched.ui; - -import android.app.ActionBar; -import android.content.Intent; -import android.graphics.Paint; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.*; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Spinner; -import android.widget.TextView; - -import com.google.samples.apps.iosched.Config; -import com.google.samples.apps.iosched.R; -import com.google.samples.apps.iosched.model.TagMetadata; -import com.google.samples.apps.iosched.provider.ScheduleContract; -import com.google.samples.apps.iosched.ui.widget.CollectionView; -import com.google.samples.apps.iosched.ui.widget.DrawShadowFrameLayout; -import com.google.samples.apps.iosched.util.AnalyticsManager; -import com.google.samples.apps.iosched.util.PrefUtils; -import com.google.samples.apps.iosched.util.UIUtils; - -import java.util.ArrayList; -import java.util.List; - -import static com.google.samples.apps.iosched.util.LogUtils.*; - -public class BrowseSessionsActivity extends BaseActivity implements SessionsFragment.Callbacks { - private static final String TAG = makeLogTag(BrowseSessionsActivity.class); - - // How is this Activity being used? - private static final int MODE_EXPLORE = 0; // as top-level "Explore" screen - private static final int MODE_TIME_FIT = 1; // showing sessions that fit in a time interval - - private static final String STATE_FILTER_0 = "STATE_FILTER_0"; - private static final String STATE_FILTER_1 = "STATE_FILTER_1"; - private static final String STATE_FILTER_2 = "STATE_FILTER_2"; - - public static final String EXTRA_FILTER_TAG = "com.google.android.iosched.extra.FILTER_TAG"; - - private int mMode = MODE_EXPLORE; - - private final static String SCREEN_LABEL = "Explore"; - - private TagMetadata mTagMetadata = null; - private boolean mSpinnerConfigured = false; - - // filter tags that are currently selected - private String[] mFilterTags = { "", "", "" }; - - // filter tags that we have to restore (as a result of Activity recreation) - private String[] mFilterTagsToRestore = { null, null, null }; - - private ExploreSpinnerAdapter mTopLevelSpinnerAdapter = new ExploreSpinnerAdapter(true); - private SessionsFragment mSessionsFrag = null; - - private DrawShadowFrameLayout mDrawShadowFrameLayout; - private View mButterBar; - - // time when the user last clicked "refresh" from the stale data butter bar - private long mLastDataStaleUserActionTime = 0L; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_browse_sessions); - getLPreviewUtils().trySetActionBar(); - - ActionBar ab = getActionBar(); - - long[] interval = ScheduleContract.Sessions.getInterval(getIntent().getData()); - if (interval != null) { - String title = UIUtils.formatIntervalTimeString(interval[0], interval[1], null, this); - ab.setTitle(title); - mMode = MODE_TIME_FIT; - /* [ANALYTICS:SCREEN] - * TRIGGER: View the Explore screen to find sessions fitting a time slot - * LABEL: 'Explore