Browse Source

Base Configuration

Philippe-Adrien Nousse 3 years ago
commit
bcac6a3b85
100 changed files with 15514 additions and 0 deletions
  1. 9 0
      .gitignore
  2. 6 0
      .gitmodules
  3. 3 0
      .idea/dictionaries/Philippe_Adrien.xml
  4. 20 0
      .idea/gradle.xml
  5. 49 0
      .idea/misc.xml
  6. 11 0
      .idea/modules.xml
  7. 12 0
      .idea/runConfigurations.xml
  8. 11 0
      ExternalLibs/Parse-SDK-Android/.codecov.yml
  9. 40 0
      ExternalLibs/Parse-SDK-Android/.gitignore
  10. 35 0
      ExternalLibs/Parse-SDK-Android/.travis.yml
  11. 65 0
      ExternalLibs/Parse-SDK-Android/CONTRIBUTING.md
  12. 34 0
      ExternalLibs/Parse-SDK-Android/LICENSE
  13. 37 0
      ExternalLibs/Parse-SDK-Android/PATENTS
  14. 52 0
      ExternalLibs/Parse-SDK-Android/Parse/build.gradle
  15. 7 0
      ExternalLibs/Parse-SDK-Android/Parse/release-proguard.pro
  16. 23 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/AndroidManifest.xml
  17. 38 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/AbstractQueryController.java
  18. 31 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/AuthenticationCallback.java
  19. 175 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CacheQueryController.java
  20. 162 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CachedCurrentInstallationController.java
  21. 290 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CachedCurrentUserController.java
  22. 44 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ConfigCallback.java
  23. 96 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ConnectivityNotifier.java
  24. 48 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CountCallback.java
  25. 44 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/DeleteCallback.java
  26. 191 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/EventuallyPin.java
  27. 139 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FileObjectStore.java
  28. 49 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FindCallback.java
  29. 49 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FunctionCallback.java
  30. 25 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmBroadcastReceiver.java
  31. 211 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmPushHandler.java
  32. 405 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmRegistrar.java
  33. 47 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetCallback.java
  34. 40 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetDataCallback.java
  35. 41 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetDataStreamCallback.java
  36. 41 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetFileCallback.java
  37. 90 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/InstallationId.java
  38. 36 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/KnownParseObjectDecoder.java
  39. 76 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/Lists.java
  40. 205 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LocalIdManager.java
  41. 48 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LocationCallback.java
  42. 118 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LocationNotifier.java
  43. 58 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LockSet.java
  44. 47 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LogInCallback.java
  45. 43 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/LogOutCallback.java
  46. 354 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ManifestInfo.java
  47. 151 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NetworkObjectController.java
  48. 148 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NetworkQueryController.java
  49. 63 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NetworkSessionController.java
  50. 142 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NetworkUserController.java
  51. 29 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NoObjectsEncoder.java
  52. 440 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/NotificationCompat.java
  53. 79 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/Numbers.java
  54. 134 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/OfflineObjectStore.java
  55. 49 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/OfflineQueryController.java
  56. 1119 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/OfflineQueryLogic.java
  57. 109 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/OfflineSQLiteOpenHelper.java
  58. 1567 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/OfflineStore.java
  59. 95 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/PLog.java
  60. 770 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/Parse.java
  61. 611 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseACL.java
  62. 95 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAddOperation.java
  63. 116 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAddUniqueOperation.java
  64. 243 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAnalytics.java
  65. 41 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAnalyticsController.java
  66. 82 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAnonymousUtils.java
  67. 91 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseAuthenticationManager.java
  68. 47 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseByteArrayHttpBody.java
  69. 29 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCallback1.java
  70. 32 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCallback2.java
  71. 28 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseClassName.java
  72. 107 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCloud.java
  73. 59 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCloudCodeController.java
  74. 688 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCommandCache.java
  75. 563 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseConfig.java
  76. 47 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseConfigController.java
  77. 356 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCorePlugins.java
  78. 51 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCountingByteArrayHttpBody.java
  79. 58 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCountingFileHttpBody.java
  80. 100 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCurrentConfigController.java
  81. 13 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCurrentInstallationController.java
  82. 41 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseCurrentUserController.java
  83. 56 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseDateFormat.java
  84. 156 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseDecoder.java
  85. 68 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseDefaultACLController.java
  86. 52 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseDeleteOperation.java
  87. 49 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseDigestUtils.java
  88. 163 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseEncoder.java
  89. 230 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseEventuallyQueue.java
  90. 291 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseException.java
  91. 39 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseExecutors.java
  92. 83 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFieldOperation.java
  93. 253 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFieldOperations.java
  94. 774 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFile.java
  95. 238 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFileController.java
  96. 50 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFileHttpBody.java
  97. 82 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFileRequest.java
  98. 546 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseFileUtils.java
  99. 336 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseGeoPoint.java
  100. 0 0
      ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseHttpClient.java

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild

+ 6 - 0
.gitmodules

@@ -0,0 +1,6 @@
+[submodule "app/libs/ParseLiveQuery-Android"]
+	path = app/libs/ParseLiveQuery-Android
+	url = https://github.com/parse-community/ParseLiveQuery-Android.git
+[submodule "app/libs/Parse-SDK-Android"]
+	path = app/libs/Parse-SDK-Android
+	url = https://github.com/parse-community/Parse-SDK-Android.git

+ 3 - 0
.idea/dictionaries/Philippe_Adrien.xml

@@ -0,0 +1,3 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="Philippe-Adrien" />
+</component>

+ 20 - 0
.idea/gradle.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/ExternalLibs/Parse-SDK-Android/Parse" />
+            <option value="$PROJECT_DIR$/ExternalLibs/ParseLiveQuery-Android/ParseLiveQuery" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 49 - 0
.idea/misc.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="NullableNotNullManager">
+    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
+    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
+    <option name="myNullables">
+      <value>
+        <list size="4">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
+        </list>
+      </value>
+    </option>
+    <option name="myNotNulls">
+      <value>
+        <list size="4">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
+        </list>
+      </value>
+    </option>
+  </component>
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state>
+        <expanded-state>
+          <State>
+            <id />
+          </State>
+        </expanded-state>
+        <selected-state>
+          <State>
+            <id>Android</id>
+          </State>
+        </selected-state>
+      </profile-state>
+    </entry>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 11 - 0
.idea/modules.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/ExternalLibs/Parse-SDK-Android/Parse/Parse.iml" filepath="$PROJECT_DIR$/ExternalLibs/Parse-SDK-Android/Parse/Parse.iml" />
+      <module fileurl="file://$PROJECT_DIR$/ParseApplication.iml" filepath="$PROJECT_DIR$/ParseApplication.iml" />
+      <module fileurl="file://$PROJECT_DIR$/ExternalLibs/ParseLiveQuery-Android/ParseLiveQuery/ParseLiveQuery.iml" filepath="$PROJECT_DIR$/ExternalLibs/ParseLiveQuery-Android/ParseLiveQuery/ParseLiveQuery.iml" />
+      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
+    </modules>
+  </component>
+</project>

+ 12 - 0
.idea/runConfigurations.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>

+ 11 - 0
ExternalLibs/Parse-SDK-Android/.codecov.yml

@@ -0,0 +1,11 @@
+coverage:
+  precision: 2
+  round: down
+  range: "45...100"
+
+  status:
+    project: 
+      default:
+        target: 45%
+    patch: yes
+    changes: no

+ 40 - 0
ExternalLibs/Parse-SDK-Android/.gitignore

@@ -0,0 +1,40 @@
+# built application files
+*.apk
+*.ap_
+
+# files for the dex VM
+*.dex
+
+# Java class files
+*.class
+
+# generated files
+bin/
+gen/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Android Studio
+.idea
+*.iml
+*.ipr
+*.iws
+classes
+gen-external-apklibs
+
+# Gradle
+.gradle
+build
+
+# Other
+.metadata
+*/bin/*
+*/gen/*
+testData
+testCache
+server.config
+
+# Jacoco
+jacoco.exec
+

+ 35 - 0
ExternalLibs/Parse-SDK-Android/.travis.yml

@@ -0,0 +1,35 @@
+branches:
+  only:
+    - master
+    - /^\d+\.\d+\.\d+$/ # regex
+
+language: android
+
+jdk:
+  - oraclejdk8
+
+before_install:
+    - pip install --user codecov
+    - mkdir "$ANDROID_HOME/licenses" || true
+    - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license"
+
+script:
+  - ./gradlew clean testDebugUnitTest jacocoTestReport
+
+after_success:
+  - ./gradlew coveralls
+  - codecov
+  - ./scripts/publish_snapshot.sh
+
+cache:
+  directories:
+    - $HOME/.gradle
+    - $HOME/.m2/repository
+
+deploy:
+  provider: script
+  script: ./gradlew bintrayUpload
+  skip_cleanup: true
+  on:
+    branch: master
+    tags: true

+ 65 - 0
ExternalLibs/Parse-SDK-Android/CONTRIBUTING.md

@@ -0,0 +1,65 @@
+# Contributing to Parse SDK for Android
+We want to make contributing to this project as easy and transparent as possible.
+
+## Code of Conduct
+Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
+
+## Our Development Process
+Most of our work will be done in public directly on GitHub. There may be changes done through our internal source control, but it will be rare and only as needed.
+
+### `master` is unsafe
+Our goal is to keep `master` stable, but there may be changes that your application may not be compatible with. We'll do our best to publicize any breaking changes, but try to use our specific releases in any production environment.
+
+### Pull Requests
+We actively welcome your pull requests. When we get one, we'll run some Parse-specific integration tests on it first. From here, we'll need to get a core member to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process.
+
+1. Fork the repo and create your branch from `master`.
+4. Add unit tests for any new code you add.
+3. If you've changed APIs, update the documentation.
+4. Ensure the test suite passes.
+5. Make sure your code lints.
+6. If you haven't already, complete the Contributor License Agreement ("CLA").
+
+### Contributor License Agreement ("CLA")
+In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects.
+
+Complete your CLA here: <https://developers.facebook.com/opensource/cla>
+
+## Bugs
+Although we try to keep developing on Parse easy, you still may run into some issues. Technical questions should be asked on [Stack Overflow][stack-overflow], and for everything else we'll be using GitHub issues.
+
+### Known Issues
+We use GitHub issues to track public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist.
+
+### Reporting New Issues
+Not all issues are SDK issues. If you're unsure whether your bug is with the SDK or backend, you can test to see if it reproduces with our [REST API][rest-api] and [Parse API Console][parse-api-console]. If it does, you can report backend bugs [here][bug-reports].
+
+To view the REST API network requests issued by the Parse SDK and responses from the Parse backend, please check out [OkHttp Interceptors][network-debugging-tool].  With this tool, you can either log network requests/responses to Android logcat, or log them to Chrome Debugger via Stetho.
+
+Details are key. The more information you provide us the easier it'll be for us to debug and the faster you'll receive a fix. Some examples of useful tidbits:
+
+* A description. What did you expect to happen and what actually happened? Why do you think that was wrong?
+* A simple unit test that fails. Refer [here][tests-dir] for examples of existing unit tests. See our [README](README.md#usage) for how to run unit tests. You can submit a pull request with your failing unit test so that our CI verifies that the test fails.
+* What version does this reproduce on? What version did it last work on?
+* [Stacktrace or GTFO][stacktrace-or-gtfo]. In all honesty, full stacktraces with line numbers make a happy developer.
+* Anything else you find relevant.
+
+### Security Bugs
+Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue.
+
+## Style Guide
+We're still working on providing a code style for your IDE and getting a linter on GitHub, but for now try to keep the following:
+
+* Most importantly, match the existing code style as much as possible.
+* Try to keep lines under 100 characters, if possible.
+
+## License
+By contributing to Parse Android SDK, you agree that your contributions will be licensed under its license.
+
+ [stack-overflow]: http://stackoverflow.com/tags/parse.com
+ [bug-reports]: https://github.com/parse-community/parse-server
+ [rest-api]: http://docs.parseplatform.org/rest/guide/
+ [network-debugging-tool]: https://github.com/square/okhttp/wiki/Interceptors
+ [parse-api-console]: http://blog.parse.com/announcements/introducing-the-parse-api-console/
+ [stacktrace-or-gtfo]: http://i.imgur.com/jacoj.jpg
+ [tests-dir]: /Parse/src/test/java/com/parse

+ 34 - 0
ExternalLibs/Parse-SDK-Android/LICENSE

@@ -0,0 +1,34 @@
+BSD License
+
+For Parse Android SDK software
+
+Copyright (c) 2015-present, Parse, LLC. 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.
+
+ * Neither the name Parse 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 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.
+
+-----
+
+As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.

+ 37 - 0
ExternalLibs/Parse-SDK-Android/PATENTS

@@ -0,0 +1,37 @@
+Additional Grant of Patent Rights Version 2
+
+"Software" means the Parse Android SDK software distributed by Parse, LLC.
+
+Parse, LLC. ("Parse") hereby grants to each recipient of the Software
+("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
+(subject to the termination provision below) license under any Necessary
+Claims, to make, have made, use, sell, offer to sell, import, and otherwise
+transfer the Software. For avoidance of doubt, no license is granted under
+Parse’s rights in any patent claims that are infringed by (i) modifications
+to the Software made by you or any third party or (ii) the Software in
+combination with any software or other technology.
+
+The license granted hereunder will terminate, automatically and without notice,
+if you (or any of your subsidiaries, corporate affiliates or agents) initiate
+directly or indirectly, or take a direct financial interest in, any Patent
+Assertion: (i) against Parse or any of its subsidiaries or corporate
+affiliates, (ii) against any party if such Patent Assertion arises in whole or
+in part from any software, technology, product or service of Parse or any of
+its subsidiaries or corporate affiliates, or (iii) against any party relating
+to the Software. Notwithstanding the foregoing, if Parse or any of its
+subsidiaries or corporate affiliates files a lawsuit alleging patent
+infringement against you in the first instance, and you respond by filing a
+patent infringement counterclaim in that lawsuit against that party that is
+unrelated to the Software, the license granted hereunder will not terminate
+under section (i) of this paragraph due to such counterclaim.
+
+A "Necessary Claim" is a claim of a patent owned by Parse that is
+necessarily infringed by the Software standing alone.
+
+A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
+or contributory infringement or inducement to infringe any patent, including a
+cross-claim or counterclaim.
+
+-----
+
+As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.

+ 52 - 0
ExternalLibs/Parse-SDK-Android/Parse/build.gradle

@@ -0,0 +1,52 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.github.kt3k.coveralls'
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1'
+    }
+}
+
+android {
+    compileSdkVersion 26
+
+    defaultConfig {
+        minSdkVersion rootProject.ext.minSdkVersion
+        targetSdkVersion rootProject.ext.targetSdkVersion
+        versionCode 1
+        versionName project.version
+        consumerProguardFiles 'release-proguard.pro'
+    }
+
+    packagingOptions {
+        exclude '**/BuildConfig.class'
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    buildTypes {
+        debug {
+            testCoverageEnabled = true
+        }
+    }
+}
+
+//ext {
+//    okhttpVersion = '3.9.1'
+//}
+
+dependencies {
+    api "com.android.support:support-annotations:$supportLibVersion"
+    api 'com.parse.bolts:bolts-tasks:1.4.0'
+    api "com.squareup.okhttp3:okhttp:$okhttpVersion"
+    testImplementation 'org.robolectric:robolectric:3.3.2'
+    testImplementation 'org.skyscreamer:jsonassert:1.5.0'
+    testImplementation 'org.mockito:mockito-core:1.10.19'
+    testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
+}

+ 7 - 0
ExternalLibs/Parse-SDK-Android/Parse/release-proguard.pro

@@ -0,0 +1,7 @@
+-keepnames class com.parse.** { *; }
+
+# Required for Parse
+-keepattributes *Annotation*
+-keepattributes Signature
+# https://github.com/square/okio#proguard
+-dontwarn okio.**

+ 23 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/AndroidManifest.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2015-present, Parse, LLC.
+  ~ All rights reserved.
+  ~
+  ~ This source code is licensed under the BSD-style license found in the
+  ~ LICENSE file in the root directory of this source tree. An additional grant
+  ~ of patent rights can be found in the PATENTS file in the same directory.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.parse">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+    <application>
+        <service
+            android:name=".PushServiceApi26"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:exported="true"/>
+    </application>
+
+</manifest>

+ 38 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/AbstractQueryController.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.util.List;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/**
+ * {@code AbstractParseQueryController} is an abstract implementation of
+ * {@link ParseQueryController}, which implements {@link ParseQueryController#getFirstAsync}.
+ */
+/** package */ abstract class AbstractQueryController implements ParseQueryController {
+
+  @Override
+  public <T extends ParseObject> Task<T> getFirstAsync(ParseQuery.State<T> state, ParseUser user,
+      Task<Void> cancellationToken) {
+    return findAsync(state, user, cancellationToken).continueWith(new Continuation<List<T>, T>() {
+      @Override
+      public T then(Task<List<T>> task) throws Exception {
+        if (task.isFaulted()) {
+          throw task.getError();
+        }
+        if (task.getResult() != null && task.getResult().size() > 0) {
+          return task.getResult().get(0);
+        }
+        throw new ParseException(ParseException.OBJECT_NOT_FOUND, "no results found for query");
+      }
+    });
+  }
+}

+ 31 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/AuthenticationCallback.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.util.Map;
+
+/**
+ * Provides a general interface for delegation of third party authentication callbacks.
+ */
+public interface AuthenticationCallback {
+  /**
+   * Called when restoring third party authentication credentials that have been serialized,
+   * such as session keys, etc.
+   * <p />
+   * <strong>Note:</strong> This will be executed on a background thread.
+   *
+   * @param authData
+   *          The auth data for the provider. This value may be {@code null} when
+   *          unlinking an account.
+   *
+   * @return {@code true} iff the {@code authData} was successfully synchronized or {@code false}
+   *          if user should no longer be associated because of bad {@code authData}.
+   */
+  boolean onRestore(Map<String, String> authData);
+}

+ 175 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CacheQueryController.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/** package */ class CacheQueryController extends AbstractQueryController {
+
+  private final NetworkQueryController networkController;
+
+  public CacheQueryController(NetworkQueryController network) {
+    networkController = network;
+  }
+
+  @Override
+  public <T extends ParseObject> Task<List<T>> findAsync(
+      final ParseQuery.State<T> state,
+      final ParseUser user,
+      final Task<Void> cancellationToken) {
+    final String sessionToken = user != null ? user.getSessionToken() : null;
+    CommandDelegate<List<T>> callbacks = new CommandDelegate<List<T>>() {
+      @Override
+      public Task<List<T>> runOnNetworkAsync() {
+        return networkController.findAsync(state, sessionToken, cancellationToken);
+      }
+
+      @Override
+      public Task<List<T>> runFromCacheAsync() {
+        return findFromCacheAsync(state, sessionToken);
+      }
+    };
+    return runCommandWithPolicyAsync(callbacks, state.cachePolicy());
+  }
+
+  @Override
+  public <T extends ParseObject> Task<Integer> countAsync(
+      final ParseQuery.State<T> state,
+      final ParseUser user,
+      final Task<Void> cancellationToken) {
+    final String sessionToken = user != null ? user.getSessionToken() : null;
+    CommandDelegate<Integer> callbacks = new CommandDelegate<Integer>() {
+      @Override
+      public Task<Integer> runOnNetworkAsync() {
+        return networkController.countAsync(state, sessionToken, cancellationToken);
+      }
+
+      @Override
+      public Task<Integer> runFromCacheAsync() {
+        return countFromCacheAsync(state, sessionToken);
+      }
+    };
+    return runCommandWithPolicyAsync(callbacks, state.cachePolicy());
+  }
+
+  /**
+   * Retrieves the results of the last time {@link ParseQuery#find()} was called on a query
+   * identical to this one.
+   *
+   * @param sessionToken The user requesting access.
+   * @return A list of {@link ParseObject}s corresponding to this query. Returns null if there is no
+   *          cache for this query.
+   */
+  private <T extends ParseObject> Task<List<T>> findFromCacheAsync(
+      final ParseQuery.State<T> state, String sessionToken) {
+    final String cacheKey = ParseRESTQueryCommand.findCommand(state, sessionToken).getCacheKey();
+    return Task.call(new Callable<List<T>>() {
+      @Override
+      public List<T> call() throws Exception {
+        JSONObject cached = ParseKeyValueCache.jsonFromKeyValueCache(cacheKey, state.maxCacheAge());
+        if (cached == null) {
+          throw new ParseException(ParseException.CACHE_MISS, "results not cached");
+        }
+        try {
+          return networkController.convertFindResponse(state, cached);
+        } catch (JSONException e) {
+          throw new ParseException(ParseException.CACHE_MISS, "the cache contains corrupted json");
+        }
+      }
+    }, Task.BACKGROUND_EXECUTOR);
+  }
+
+  /**
+   * Retrieves the results of the last time {@link ParseQuery#count()} was called on a query
+   * identical to this one.
+   *
+   * @param sessionToken The user requesting access.
+   * @return A list of {@link ParseObject}s corresponding to this query. Returns null if there is no
+   *          cache for this query.
+   */
+  private <T extends ParseObject> Task<Integer> countFromCacheAsync(
+      final ParseQuery.State<T> state, String sessionToken) {
+    final String cacheKey = ParseRESTQueryCommand.countCommand(state, sessionToken).getCacheKey();
+    return Task.call(new Callable<Integer>() {
+      @Override
+      public Integer call() throws Exception {
+        JSONObject cached = ParseKeyValueCache.jsonFromKeyValueCache(cacheKey, state.maxCacheAge());
+        if (cached == null) {
+          throw new ParseException(ParseException.CACHE_MISS, "results not cached");
+        }
+        try {
+          return cached.getInt("count");
+        } catch (JSONException e) {
+          throw new ParseException(ParseException.CACHE_MISS, "the cache contains corrupted json");
+        }
+      }
+    }, Task.BACKGROUND_EXECUTOR);
+  }
+
+  private <TResult> Task<TResult> runCommandWithPolicyAsync(final CommandDelegate<TResult> c,
+      ParseQuery.CachePolicy policy) {
+    switch (policy) {
+      case IGNORE_CACHE:
+      case NETWORK_ONLY:
+        return c.runOnNetworkAsync();
+      case CACHE_ONLY:
+        return c.runFromCacheAsync();
+      case CACHE_ELSE_NETWORK:
+        return c.runFromCacheAsync().continueWithTask(new Continuation<TResult, Task<TResult>>() {
+          @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+          @Override
+          public Task<TResult> then(Task<TResult> task) throws Exception {
+            if (task.getError() instanceof ParseException) {
+              return c.runOnNetworkAsync();
+            }
+            return task;
+          }
+        });
+      case NETWORK_ELSE_CACHE:
+        return c.runOnNetworkAsync().continueWithTask(new Continuation<TResult, Task<TResult>>() {
+          @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+          @Override
+          public Task<TResult> then(Task<TResult> task) throws Exception {
+            Exception error = task.getError();
+            if (error instanceof ParseException &&
+                ((ParseException) error).getCode() == ParseException.CONNECTION_FAILED) {
+              return c.runFromCacheAsync();
+            }
+            // Either the query succeeded, or there was an an error with the query, not the
+            // network
+            return task;
+          }
+        });
+      case CACHE_THEN_NETWORK:
+        throw new RuntimeException(
+            "You cannot use the cache policy CACHE_THEN_NETWORK with find()");
+      default:
+        throw new RuntimeException("Unknown cache policy: " + policy);
+    }
+  }
+
+  /**
+   * A callback that will be used to tell runCommandWithPolicy how to perform the command on the
+   * network and form the cache.
+   */
+  private interface CommandDelegate<T> {
+    // Fetches data from the network.
+    Task<T> runOnNetworkAsync();
+
+    // Fetches data from the cache.
+    Task<T> runFromCacheAsync();
+  }
+}

+ 162 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CachedCurrentInstallationController.java

@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/** package */ class CachedCurrentInstallationController
+    implements ParseCurrentInstallationController {
+
+  /* package */ static final String TAG = "com.parse.CachedCurrentInstallationController";
+
+  /*
+   * Note about lock ordering:
+   *
+   * You must NOT acquire the ParseInstallation instance mutex (the "mutex" field in ParseObject)
+   * while holding this current installation lock. (We used to use the ParseInstallation.class lock,
+   * but moved on to an explicit lock object since anyone could acquire the ParseInstallation.class
+   * lock as ParseInstallation is a public class.) Acquiring the instance mutex while holding this
+   * current installation lock will lead to a deadlock. Here is an example:
+   * https://phabricator.fb.com/P3251091
+   */
+  private final Object mutex = new Object();
+
+  private final TaskQueue taskQueue = new TaskQueue();
+
+  private final ParseObjectStore<ParseInstallation> store;
+  private final InstallationId installationId;
+
+  // The "current installation" is the installation for this device. Protected by
+  // mutex.
+  /* package for test */ ParseInstallation currentInstallation;
+
+  public CachedCurrentInstallationController(
+      ParseObjectStore<ParseInstallation> store, InstallationId installationId) {
+    this.store = store;
+    this.installationId = installationId;
+  }
+
+  @Override
+  public Task<Void> setAsync(final ParseInstallation installation) {
+    if (!isCurrent(installation)) {
+      return Task.forResult(null);
+    }
+
+    return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
+      @Override
+      public Task<Void> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            return store.setAsync(installation);
+          }
+        }).continueWithTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            installationId.set(installation.getInstallationId());
+            return task;
+          }
+        }, ParseExecutors.io());
+      }
+    });
+  }
+
+  @Override
+  public Task<ParseInstallation> getAsync() {
+    synchronized (mutex) {
+      if (currentInstallation != null) {
+        return Task.forResult(currentInstallation);
+      }
+    }
+
+    return taskQueue.enqueue(new Continuation<Void, Task<ParseInstallation>>() {
+      @Override
+      public Task<ParseInstallation> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<ParseInstallation>>() {
+          @Override
+          public Task<ParseInstallation> then(Task<Void> task) throws Exception {
+            synchronized (mutex) {
+              if (currentInstallation != null) {
+                return Task.forResult(currentInstallation);
+              }
+            }
+
+            return store.getAsync().continueWith(new Continuation<ParseInstallation, ParseInstallation>() {
+              @Override
+              public ParseInstallation then(Task<ParseInstallation> task) throws Exception {
+                ParseInstallation current = task.getResult();
+                if (current == null) {
+                  current = ParseObject.create(ParseInstallation.class);
+                  current.updateDeviceInfo(installationId);
+                } else {
+                  installationId.set(current.getInstallationId());
+                  PLog.v(TAG, "Successfully deserialized Installation object");
+                }
+
+                synchronized (mutex) {
+                  currentInstallation = current;
+                }
+                return current;
+              }
+            }, ParseExecutors.io());
+          }
+        });
+      }
+    });
+  }
+
+  @Override
+  public Task<Boolean> existsAsync() {
+    synchronized (mutex) {
+      if (currentInstallation != null) {
+        return Task.forResult(true);
+      }
+    }
+
+    return taskQueue.enqueue(new Continuation<Void, Task<Boolean>>() {
+      @Override
+      public Task<Boolean> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<Boolean>>() {
+          @Override
+          public Task<Boolean> then(Task<Void> task) throws Exception {
+            return store.existsAsync();
+          }
+        });
+      }
+    });
+  }
+
+  @Override
+  public void clearFromMemory() {
+    synchronized (mutex) {
+      currentInstallation = null;
+    }
+  }
+
+  @Override
+  public void clearFromDisk() {
+    synchronized (mutex) {
+      currentInstallation = null;
+    }
+    try {
+      installationId.clear();
+      ParseTaskUtils.wait(store.deleteAsync());
+    } catch (ParseException e) {
+      // ignored
+    }
+  }
+
+  @Override
+  public boolean isCurrent(ParseInstallation installation) {
+    synchronized (mutex) {
+      return  currentInstallation == installation;
+    }
+  }
+}

+ 290 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CachedCurrentUserController.java

@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/** package */ class CachedCurrentUserController implements ParseCurrentUserController {
+
+  /**
+   * Lock used to synchronize current user modifications and access.
+   *
+   * Note about lock ordering:
+   *
+   * You must NOT acquire the ParseUser instance mutex (the "mutex" field in ParseObject) while
+   * holding this static initialization lock. Doing so will cause a deadlock. Here's an example:
+   * https://phabricator.fb.com/P17182641
+   */
+  private final Object mutex = new Object();
+  private final TaskQueue taskQueue = new TaskQueue();
+
+  private final ParseObjectStore<ParseUser> store;
+
+  /* package */ ParseUser currentUser;
+  // Whether currentUser is known to match the serialized version on disk. This is useful for saving
+  // a filesystem check if you try to load currentUser frequently while there is none on disk.
+  /* package */ boolean currentUserMatchesDisk = false;
+
+  public CachedCurrentUserController(ParseObjectStore<ParseUser> store) {
+    this.store = store;
+  }
+
+  @Override
+  public Task<Void> setAsync(final ParseUser user) {
+    return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
+      @Override
+      public Task<Void> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            ParseUser oldCurrentUser;
+            synchronized (mutex) {
+              oldCurrentUser = currentUser;
+            }
+
+            if (oldCurrentUser != null && oldCurrentUser != user) {
+              // We don't need to revoke the token since we're not explicitly calling logOut
+              // We don't need to remove persisted files since we're overwriting them
+              return oldCurrentUser.logOutAsync(false).continueWith(new Continuation<Void, Void>() {
+                @Override
+                public Void then(Task<Void> task) throws Exception {
+                  return null; // ignore errors
+                }
+              });
+            }
+            return task;
+          }
+        }).onSuccessTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            user.setIsCurrentUser(true);
+            return user.synchronizeAllAuthDataAsync();
+          }
+        }).onSuccessTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            return store.setAsync(user).continueWith(new Continuation<Void, Void>() {
+              @Override
+              public Void then(Task<Void> task) throws Exception {
+                synchronized (mutex) {
+                  currentUserMatchesDisk = !task.isFaulted();
+                  currentUser = user;
+                }
+                return null;
+              }
+            });
+          }
+        });
+      }
+    });
+  }
+
+  @Override
+  public Task<Void> setIfNeededAsync(ParseUser user) {
+    synchronized (mutex) {
+      if (!user.isCurrentUser() || currentUserMatchesDisk) {
+        return Task.forResult(null);
+      }
+    }
+
+    return setAsync(user);
+  }
+
+  @Override
+  public Task<ParseUser> getAsync() {
+    return getAsync(ParseUser.isAutomaticUserEnabled());
+  }
+
+  @Override
+  public Task<Boolean> existsAsync() {
+    synchronized (mutex) {
+      if (currentUser != null) {
+        return Task.forResult(true);
+      }
+    }
+
+    return taskQueue.enqueue(new Continuation<Void, Task<Boolean>>() {
+      @Override
+      public Task<Boolean> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<Boolean>>() {
+          @Override
+          public Task<Boolean> then(Task<Void> task) throws Exception {
+            return store.existsAsync();
+          }
+        });
+      }
+    });
+  }
+
+  @Override
+  public boolean isCurrent(ParseUser user) {
+    synchronized (mutex) {
+      return currentUser == user;
+    }
+  }
+
+  @Override
+  public void clearFromMemory() {
+    synchronized (mutex) {
+      currentUser = null;
+      currentUserMatchesDisk = false;
+    }
+  }
+
+  @Override
+  public void clearFromDisk() {
+    synchronized (mutex) {
+      currentUser = null;
+      currentUserMatchesDisk = false;
+    }
+    try {
+      ParseTaskUtils.wait(store.deleteAsync());
+    } catch (ParseException e) {
+      // ignored
+    }
+  }
+
+  @Override
+  public Task<String> getCurrentSessionTokenAsync() {
+    return getAsync(false).onSuccess(new Continuation<ParseUser, String>() {
+      @Override
+      public String then(Task<ParseUser> task) throws Exception {
+        ParseUser user = task.getResult();
+        return user != null ? user.getSessionToken() : null;
+      }
+    });
+  }
+
+  @Override
+  public Task<Void> logOutAsync() {
+    return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
+      @Override
+      public Task<Void> then(Task<Void> toAwait) throws Exception {
+        // We can parallelize disk and network work, but only after we restore the current user from
+        // disk.
+        final Task<ParseUser> userTask = getAsync(false);
+        return Task.whenAll(Arrays.asList(userTask, toAwait)).continueWithTask(new Continuation<Void, Task<Void>>() {
+          @Override
+          public Task<Void> then(Task<Void> task) throws Exception {
+            Task<Void> logOutTask = userTask.onSuccessTask(new Continuation<ParseUser, Task<Void>>() {
+              @Override
+              public Task<Void> then(Task<ParseUser> task) throws Exception {
+                ParseUser user = task.getResult();
+                if (user == null) {
+                  return task.cast();
+                }
+                return user.logOutAsync();
+              }
+            });
+
+            Task<Void> diskTask = store.deleteAsync().continueWith(new Continuation<Void, Void>() {
+              @Override
+              public Void then(Task<Void> task) throws Exception {
+                boolean deleted = !task.isFaulted();
+                synchronized (mutex) {
+                  currentUserMatchesDisk = deleted;
+                  currentUser = null;
+                }
+                return null;
+              }
+            });
+            return Task.whenAll(Arrays.asList(logOutTask, diskTask));
+          }
+        });
+      }
+    });
+  }
+
+  @Override
+  public Task<ParseUser> getAsync(final boolean shouldAutoCreateUser) {
+    synchronized (mutex) {
+      if (currentUser != null) {
+        return Task.forResult(currentUser);
+      }
+    }
+
+    return taskQueue.enqueue(new Continuation<Void, Task<ParseUser>>() {
+      @Override
+      public Task<ParseUser> then(Task<Void> toAwait) throws Exception {
+        return toAwait.continueWithTask(new Continuation<Void, Task<ParseUser>>() {
+          @Override
+          public Task<ParseUser> then(Task<Void> ignored) throws Exception {
+            ParseUser current;
+            boolean matchesDisk;
+            synchronized (mutex) {
+              current = currentUser;
+              matchesDisk = currentUserMatchesDisk;
+            }
+
+            if (current != null) {
+              return Task.forResult(current);
+            }
+
+            if (matchesDisk) {
+              if (shouldAutoCreateUser) {
+                return Task.forResult(lazyLogIn());
+              }
+              return null;
+            }
+
+            return store.getAsync().continueWith(new Continuation<ParseUser, ParseUser>() {
+              @Override
+              public ParseUser then(Task<ParseUser> task) throws Exception {
+                ParseUser current = task.getResult();
+                boolean matchesDisk = !task.isFaulted();
+
+                synchronized (mutex) {
+                  currentUser = current;
+                  currentUserMatchesDisk = matchesDisk;
+                }
+
+                if (current != null) {
+                  synchronized (current.mutex) {
+                    current.setIsCurrentUser(true);
+                  }
+                  return current;
+                }
+
+                if (shouldAutoCreateUser) {
+                  return lazyLogIn();
+                }
+                return null;
+              }
+            });
+          }
+        });
+      }
+    });
+  }
+
+  private ParseUser lazyLogIn() {
+    Map<String, String> authData = ParseAnonymousUtils.getAuthData();
+    return lazyLogIn(ParseAnonymousUtils.AUTH_TYPE, authData);
+  }
+
+  /* package for tests */ ParseUser lazyLogIn(String authType, Map<String, String> authData) {
+    // Note: if authType != ParseAnonymousUtils.AUTH_TYPE the user is not "lazy".
+    ParseUser user = ParseObject.create(ParseUser.class);
+    synchronized (user.mutex) {
+      user.setIsCurrentUser(true);
+      user.putAuthData(authType, authData);
+    }
+
+    synchronized (mutex) {
+      currentUserMatchesDisk = false;
+      currentUser = user;
+    }
+
+    return user;
+  }
+}

+ 44 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ConfigCallback.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code ConfigCallback} is used to run code after {@link ParseConfig#getInBackground()} is used
+ * to fetch a new configuration object from the server in a background thread.
+ * <p>
+ * The easiest way to use a {@code ConfigCallback} is through an anonymous inner class. Override the
+ * {@code done} function to specify what the callback should do after the fetch is complete.
+ * The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * <pre>
+ * ParseConfig.getInBackground(new ConfigCallback() {
+ *   public void done(ParseConfig config, ParseException e) {
+ *     if (e == null) {
+ *       configFetchSuccess(object);
+ *     } else {
+ *       configFetchFailed(e);
+ *     }
+ *   }
+ * });
+ * </pre>
+ */
+public interface ConfigCallback extends ParseCallback2<ParseConfig, ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   *
+   * @param config
+   *          A new {@code ParseConfig} instance from the server, or {@code null} if it did not
+   *          succeed.
+   * @param e
+   *          The exception raised by the fetch, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(ParseConfig config, ParseException e);
+}

+ 96 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ConnectivityNotifier.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ReceiverCallNotAllowedException;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** package */ class ConnectivityNotifier extends BroadcastReceiver {
+  private static final String TAG = "com.parse.ConnectivityNotifier";
+  public interface ConnectivityListener {
+    void networkConnectivityStatusChanged(Context context, Intent intent);
+  }
+
+  private static final ConnectivityNotifier singleton = new ConnectivityNotifier();
+  public static ConnectivityNotifier getNotifier(Context context) {
+    singleton.tryToRegisterForNetworkStatusNotifications(context);
+    return singleton;
+  }
+
+  public static boolean isConnected(Context context) {
+    ConnectivityManager connectivityManager =
+        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+    if (connectivityManager == null) {
+      return false;
+    }
+
+    NetworkInfo network = connectivityManager.getActiveNetworkInfo();
+    return network != null && network.isConnected();
+  }
+
+  private Set<ConnectivityListener> listeners = new HashSet<>();
+  private boolean hasRegisteredReceiver = false;
+  private final Object lock = new Object();
+  
+  public void addListener(ConnectivityListener delegate) {
+    synchronized (lock) {
+      listeners.add(delegate);
+    }
+  }
+  
+  public void removeListener(ConnectivityListener delegate) {
+    synchronized (lock) {
+      listeners.remove(delegate);
+    }
+  }
+  
+  private boolean tryToRegisterForNetworkStatusNotifications(Context context) {
+    synchronized (lock) {
+      if (hasRegisteredReceiver) {
+        return true;
+      }
+      
+      try {
+        if (context == null) {
+          return false;
+        }
+        context = context.getApplicationContext();
+        context.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+        hasRegisteredReceiver = true;
+        return true;
+      } catch (ReceiverCallNotAllowedException e) {
+        // In practice, this only happens with the push service, which will trigger a retry soon afterwards.
+        PLog.v(TAG, "Cannot register a broadcast receiver because the executing " +
+            "thread is currently in a broadcast receiver. Will try again later.");
+        return false;
+      }
+    }
+  }
+  
+  @Override
+  public void onReceive(Context context, Intent intent) {
+    List<ConnectivityListener> listenersCopy;
+    synchronized (lock) {
+      listenersCopy = new ArrayList<>(listeners);
+    }
+    for (ConnectivityListener delegate : listenersCopy) {
+      delegate.networkConnectivityStatusChanged(context, intent);
+    }
+  }
+}

+ 48 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/CountCallback.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code CountCallback} is used to run code after a {@link ParseQuery} is used to count objects
+ * matching a query in a background thread.
+ * <p/>
+ * The easiest way to use a {@code CountCallback} is through an anonymous inner class. Override the
+ * {@code done} function to specify what the callback should do after the count is complete.
+ * The {@code done} function will be run in the UI thread, while the count happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * For example, this sample code counts objects of class {@code "MyClass"}. It calls a
+ * different function depending on whether the count succeeded or not.
+ * <p/>
+ * <pre>
+ * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery(&quot;MyClass&quot;);
+ * query.countInBackground(new CountCallback() {
+ *   public void done(int count, ParseException e) {
+ *     if (e == null) {
+ *       objectsWereCountedSuccessfully(count);
+ *     } else {
+ *       objectCountingFailed();
+ *     }
+ *   }
+ * });
+ * </pre>
+ */
+// FYI, this does not extend ParseCallback2 since the first param is `int`, which can't be used
+// in a generic.
+public interface CountCallback {
+  /**
+   * Override this function with the code you want to run after the count is complete.
+   * 
+   * @param count
+   *          The number of objects matching the query, or -1 if it failed.
+   * @param e
+   *          The exception raised by the count, or null if it succeeded.
+   */
+  void done(int count, ParseException e);
+}

+ 44 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/DeleteCallback.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code DeleteCallback} is used to run code after saving a {@link ParseObject} in a background
+ * thread.
+ * <p/>
+ * The easiest way to use a {@code DeleteCallback} is through an anonymous inner class. Override the
+ * {@code done} function to specify what the callback should do after the delete is complete.
+ * The {@code done} function will be run in the UI thread, while the delete happens in a
+ * background thread. This ensures that the UI does not freeze while the delete happens.
+ * <p/>
+ * For example, this sample code deletes the object {@code myObject} and calls a different
+ * function depending on whether the save succeeded or not.
+ * <p/>
+ * <pre>
+ * myObject.deleteInBackground(new DeleteCallback() {
+ *   public void done(ParseException e) {
+ *     if (e == null) {
+ *       myObjectWasDeletedSuccessfully();
+ *     } else {
+ *       myObjectDeleteDidNotSucceed();
+ *     }
+ *   }
+ * });
+ * </pre>
+ */
+public interface DeleteCallback extends ParseCallback1<ParseException> {
+  /**
+   * Override this function with the code you want to run after the delete is complete.
+   * 
+   * @param e
+   *          The exception raised by the delete, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(ParseException e);
+}

+ 191 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/EventuallyPin.java

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import com.parse.http.ParseHttpRequest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import bolts.Continuation;
+import bolts.Task;
+
+/**
+ * Properties
+ * - time
+ *    Used for sort order when querying for all EventuallyPins
+ * - type
+ *    TYPE_SAVE or TYPE_DELETE
+ * - object
+ *    The object that the operation should notify when complete
+ * - operationSetUUID
+ *    The operationSet to be completed
+ * - sessionToken
+ *    The user that instantiated the operation
+ */
+@ParseClassName("_EventuallyPin")
+/** package */ class EventuallyPin extends ParseObject {
+
+  public static final String PIN_NAME = "_eventuallyPin";
+
+  public static final int TYPE_SAVE = 1;
+  public static final int TYPE_DELETE = 2;
+  public static final int TYPE_COMMAND = 3;
+
+  public EventuallyPin() {
+    super("_EventuallyPin");
+  }
+
+  @Override
+  boolean needsDefaultACL() {
+    return false;
+  }
+
+  public String getUUID() {
+    return getString("uuid");
+  }
+
+  public int getType() {
+    return getInt("type");
+  }
+
+  public ParseObject getObject() {
+    return getParseObject("object");
+  }
+
+  public String getOperationSetUUID() {
+    return getString("operationSetUUID");
+  }
+
+  public String getSessionToken() {
+    return getString("sessionToken");
+  }
+
+  public ParseRESTCommand getCommand() throws JSONException {
+    JSONObject json = getJSONObject("command");
+    ParseRESTCommand command = null;
+    if (ParseRESTCommand.isValidCommandJSONObject(json)) {
+      command = ParseRESTCommand.fromJSONObject(json);
+    } else if (ParseRESTCommand.isValidOldFormatCommandJSONObject(json)) {
+      // do nothing
+    } else {
+      throw new JSONException("Failed to load command from JSON.");
+    }
+    return command;
+  }
+
+  public static Task<EventuallyPin> pinEventuallyCommand(ParseObject object,
+      ParseRESTCommand command) {
+    int type = TYPE_COMMAND;
+    JSONObject json = null;
+    if (command.httpPath.startsWith("classes")) {
+      if (command.method == ParseHttpRequest.Method.POST ||
+          command.method == ParseHttpRequest.Method.PUT) {
+        type = TYPE_SAVE;
+      } else if (command.method == ParseHttpRequest.Method.DELETE) {
+        type = TYPE_DELETE;
+      }
+    } else {
+      json = command.toJSONObject();
+    }
+    return pinEventuallyCommand(
+        type,
+        object,
+        command.getOperationSetUUID(),
+        command.getSessionToken(),
+        json);
+  }
+
+  /**
+   * @param type
+   *          Type of the command: TYPE_SAVE, TYPE_DELETE, TYPE_COMMAND
+   * @param obj
+   *          (Optional) Object the operation is being executed on. Required for TYPE_SAVE and
+   *          TYPE_DELETE.
+   * @param operationSetUUID
+   *          (Optional) UUID of the ParseOperationSet that is paired with the ParseCommand.
+   *          Required for TYPE_SAVE and TYPE_DELETE.
+   * @param sessionToken
+   *          (Optional) The sessionToken for the command. Required for TYPE_SAVE and TYPE_DELETE.
+   * @param command
+   *          (Optional) JSON representation of the ParseCommand. Required for TYPE_COMMAND.
+   * @return
+   *          Returns a task that is resolved when the command is pinned.
+   */
+  private static Task<EventuallyPin> pinEventuallyCommand(int type, ParseObject obj,
+      String operationSetUUID, String sessionToken, JSONObject command) {
+    final EventuallyPin pin = new EventuallyPin();
+    pin.put("uuid", UUID.randomUUID().toString());
+    pin.put("time", new Date());
+    pin.put("type", type);
+    if (obj != null) {
+      pin.put("object", obj);
+    }
+    if (operationSetUUID != null) {
+      pin.put("operationSetUUID", operationSetUUID);
+    }
+    if (sessionToken != null) {
+      pin.put("sessionToken", sessionToken);
+    }
+    if (command != null) {
+      pin.put("command", command);
+    }
+    return pin.pinInBackground(PIN_NAME).continueWith(new Continuation<Void, EventuallyPin>() {
+      @Override
+      public EventuallyPin then(Task<Void> task) throws Exception {
+        return pin;
+      }
+    });
+  }
+
+  public static Task<List<EventuallyPin>> findAllPinned() {
+    return findAllPinned(null);
+  }
+
+  public static Task<List<EventuallyPin>> findAllPinned(Collection<String> excludeUUIDs) {
+    ParseQuery<EventuallyPin> query = new ParseQuery<>(EventuallyPin.class)
+        .fromPin(PIN_NAME)
+        .ignoreACLs()
+        .orderByAscending("time");
+
+    if (excludeUUIDs != null) {
+      query.whereNotContainedIn("uuid", excludeUUIDs);
+    }
+
+    // We need pass in a null user because we don't want the query to fetch the current user
+    // from LDS.
+    return query.findInBackground().continueWithTask(new Continuation<List<EventuallyPin>, Task<List<EventuallyPin>>>() {
+      @Override
+      public Task<List<EventuallyPin>> then(Task<List<EventuallyPin>> task) throws Exception {
+        final List<EventuallyPin> pins = task.getResult();
+        List<Task<Void>> tasks = new ArrayList<>();
+
+        for (EventuallyPin pin : pins) {
+          ParseObject object = pin.getObject();
+          if (object != null) {
+            tasks.add(object.fetchFromLocalDatastoreAsync().makeVoid());
+          }
+        }
+
+        return Task.whenAll(tasks).continueWithTask(new Continuation<Void, Task<List<EventuallyPin>>>() {
+          @Override
+          public Task<List<EventuallyPin>> then(Task<Void> task) throws Exception {
+            return Task.forResult(pins);
+          }
+        });
+      }
+    });
+  }
+}

+ 139 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FileObjectStore.java

@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import bolts.Task;
+
+/** package */ class FileObjectStore<T extends ParseObject> implements ParseObjectStore<T> {
+
+  private static ParseObjectSubclassingController getSubclassingController() {
+    return ParseCorePlugins.getInstance().getSubclassingController();
+  }
+
+  /**
+   * Saves the {@code ParseObject} to the a file on disk as JSON in /2/ format.
+   *
+   * @param coder
+   *          Current coder to encode the ParseObject.
+   * @param current
+   *          ParseObject which needs to be saved to disk.
+   * @param file
+   *          The file to save the object to.
+   *
+   * @see #getFromDisk(ParseObjectCurrentCoder, File, ParseObject.State.Init)
+   */
+  private static void saveToDisk(
+      ParseObjectCurrentCoder coder, ParseObject current, File file) {
+    JSONObject json = coder.encode(current.getState(), null, PointerEncoder.get());
+    try {
+      ParseFileUtils.writeJSONObjectToFile(file, json);
+    } catch (IOException e) {
+      //TODO(grantland): We should do something if this fails...
+    }
+  }
+
+  /**
+   * Retrieves a {@code ParseObject} from a file on disk in /2/ format.
+   *
+   * @param coder
+   *          Current coder to decode the ParseObject.
+   * @param file
+   *          The file to retrieve the object from.
+   * @param builder
+   *          An empty builder which is used to generate a empty state and rebuild a ParseObject.
+   * @return The {@code ParseObject} that was retrieved. If the file wasn't found, or the contents
+   *         of the file is an invalid {@code ParseObject}, returns {@code null}.
+   *
+   * @see #saveToDisk(ParseObjectCurrentCoder, ParseObject, File)
+   */
+  private static <T extends ParseObject> T getFromDisk(
+      ParseObjectCurrentCoder coder, File file, ParseObject.State.Init builder) {
+    JSONObject json;
+    try {
+      json = ParseFileUtils.readFileToJSONObject(file);
+    } catch (IOException | JSONException e) {
+      return null;
+    }
+
+    ParseObject.State newState = coder.decode(builder, json, ParseDecoder.get())
+        .isComplete(true)
+        .build();
+    return ParseObject.from(newState);
+  }
+
+  private final String className;
+  private final File file;
+  private final ParseObjectCurrentCoder coder;
+
+  public FileObjectStore(Class<T> clazz, File file, ParseObjectCurrentCoder coder) {
+    this(getSubclassingController().getClassName(clazz), file, coder);
+  }
+
+  public FileObjectStore(String className, File file, ParseObjectCurrentCoder coder) {
+    this.className = className;
+    this.file = file;
+    this.coder = coder;
+  }
+
+  @Override
+  public Task<Void> setAsync(final T object) {
+    return Task.call(new Callable<Void>() {
+      @Override
+      public Void call() throws Exception {
+        saveToDisk(coder, object, file);
+        //TODO (grantland): check to see if this failed? We currently don't for legacy reasons.
+        return null;
+      }
+    }, ParseExecutors.io());
+  }
+
+  @Override
+  public Task<T> getAsync() {
+    return Task.call(new Callable<T>() {
+      @Override
+      public T call() throws Exception {
+        if (!file.exists()) {
+          return null;
+        }
+        return getFromDisk(coder, file, ParseObject.State.newBuilder(className));
+      }
+    }, ParseExecutors.io());
+  }
+
+  @Override
+  public Task<Boolean> existsAsync() {
+    return Task.call(new Callable<Boolean>() {
+      @Override
+      public Boolean call() throws Exception {
+        return file.exists();
+      }
+    }, ParseExecutors.io());
+  }
+
+  @Override
+  public Task<Void> deleteAsync() {
+    return Task.call(new Callable<Void>() {
+      @Override
+      public Void call() throws Exception {
+        if (file.exists() && !ParseFileUtils.deleteQuietly(file)) {
+          throw new RuntimeException("Unable to delete");
+        }
+
+        return null;
+      }
+    }, ParseExecutors.io());
+  }
+}

+ 49 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FindCallback.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.util.List;
+
+/**
+ * A {@code FindCallback} is used to run code after a {@link ParseQuery} is used to fetch a list of
+ * {@link ParseObject}s in a background thread.
+ * <p/>
+ * The easiest way to use a {@code FindCallback} is through an anonymous inner class. Override the
+ * {@code done} function to specify what the callback should do after the fetch is complete.
+ * The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * For example, this sample code fetches all objects of class {@code "MyClass"}. It calls a
+ * different function depending on whether the fetch succeeded or not.
+ * <p/>
+ * <pre>
+ * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery(&quot;MyClass&quot;);
+ * query.findInBackground(new FindCallback&lt;ParseObject&gt;() {
+ *   public void done(List&lt;ParseObject&gt; objects, ParseException e) {
+ *     if (e == null) {
+ *       objectsWereRetrievedSuccessfully(objects);
+ *     } else {
+ *       objectRetrievalFailed();
+ *     }
+ *   }
+ * });
+ * </pre>
+ */
+public interface FindCallback<T extends ParseObject> extends ParseCallback2<List<T>, ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   * 
+   * @param objects
+   *          The objects that were retrieved, or null if it did not succeed.
+   * @param e
+   *          The exception raised by the save, or null if it succeeded.
+   */
+  @Override
+  void done(List<T> objects, ParseException e);
+}

+ 49 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/FunctionCallback.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code FunctionCallback} is used to run code after {@link ParseCloud#callFunction} is used to
+ * run a Cloud Function in a background thread.
+ * <p/>
+ * The easiest way to use a {@code FunctionCallback} is through an anonymous inner class. Override
+ * the {@code done} function to specify what the callback should do after the cloud function is
+ * complete. The {@code done} function will be run in the UI thread, while the fetch happens in
+ * a background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * For example, this sample code calls a cloud function {@code "MyFunction"} with
+ * {@code params} and calls a different function depending on whether the function succeeded.
+ * <p/>
+ * <pre>
+ * ParseCloud.callFunctionInBackground(&quot;MyFunction&quot;new, params, FunctionCallback<ParseObject>() {
+ *   public void done(ParseObject object, ParseException e) {
+ *     if (e == null) {
+ *       cloudFunctionSucceeded(object);
+ *     } else {
+ *       cloudFunctionFailed();
+ *     }
+ *   }
+ * });
+ * </pre>
+ * 
+ * @param <T>
+ *          The type of object returned by the Cloud Function.
+ */
+public interface FunctionCallback<T> extends ParseCallback2<T, ParseException> {
+  /**
+   * Override this function with the code you want to run after the cloud function is complete.
+   * 
+   * @param object
+   *          The object that was returned by the cloud function.
+   * @param e
+   *          The exception raised by the cloud call, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(T object, ParseException e);
+}

+ 25 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmBroadcastReceiver.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.CallSuper;
+
+/**
+ * @exclude
+ */
+public class GcmBroadcastReceiver extends BroadcastReceiver {
+  @Override
+  @CallSuper
+  public void onReceive(Context context, Intent intent) {
+    PushServiceUtils.runService(context, intent);
+  }
+}

+ 211 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmPushHandler.java

@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import bolts.Task;
+
+/**
+ * Proxy Service while running in GCM mode.
+ *
+ * We use an {@link ExecutorService} so that we can operate like a ghetto
+ * {@link android.app.IntentService} where all incoming {@link Intent}s will be handled
+ * sequentially.
+ */
+/** package */ class GcmPushHandler implements PushHandler {
+  private static final String TAG = "GcmPushHandler";
+
+  static final String REGISTER_RESPONSE_ACTION = "com.google.android.c2dm.intent.REGISTRATION";
+  static final String RECEIVE_PUSH_ACTION = "com.google.android.c2dm.intent.RECEIVE";
+
+  GcmPushHandler() {}
+
+  @NonNull
+  @Override
+  public SupportLevel isSupported() {
+    if (!ManifestInfo.isGooglePlayServicesAvailable()) {
+      return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
+    }
+    return getManifestSupportLevel();
+  }
+
+  private SupportLevel getManifestSupportLevel() {
+    Context context = Parse.getApplicationContext();
+    String[] requiredPermissions = new String[] {
+        "android.permission.INTERNET",
+        "android.permission.ACCESS_NETWORK_STATE",
+        "android.permission.WAKE_LOCK",
+        "com.google.android.c2dm.permission.RECEIVE",
+        context.getPackageName() + ".permission.C2D_MESSAGE"
+    };
+
+    if (!ManifestInfo.hasRequestedPermissions(context, requiredPermissions)) {
+      return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
+    }
+
+    String packageName = context.getPackageName();
+    String rcvrPermission = "com.google.android.c2dm.permission.SEND";
+    Intent[] intents = new Intent[] {
+        new Intent(GcmPushHandler.RECEIVE_PUSH_ACTION)
+            .setPackage(packageName)
+            .addCategory(packageName),
+        new Intent(GcmPushHandler.REGISTER_RESPONSE_ACTION)
+            .setPackage(packageName)
+            .addCategory(packageName),
+    };
+
+    if (!ManifestInfo.checkReceiver(GcmBroadcastReceiver.class, rcvrPermission, intents)) {
+      return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
+    }
+
+    String[] optionalPermissions = new String[] {
+        "android.permission.VIBRATE"
+    };
+
+    if (!ManifestInfo.hasGrantedPermissions(context, optionalPermissions)) {
+      return SupportLevel.MISSING_OPTIONAL_DECLARATIONS;
+    }
+
+    return SupportLevel.SUPPORTED;
+  }
+
+  @Nullable
+  @Override
+  public String getWarningMessage(SupportLevel level) {
+    switch (level) {
+      case SUPPORTED: return null;
+      case MISSING_OPTIONAL_DECLARATIONS: return "Using GCM for Parse Push, " +
+          "but the app manifest is missing some optional " +
+          "declarations that should be added for maximum reliability. Please " +
+          getWarningMessage();
+      case MISSING_REQUIRED_DECLARATIONS:
+        if (ManifestInfo.isGooglePlayServicesAvailable()) {
+          return "Cannot use GCM for push because the app manifest is missing some " +
+              "required declarations. Please " + getWarningMessage();
+        } else {
+          return "Cannot use GCM for push on this device because Google Play " +
+              "Services is not available. Install Google Play Services from the Play Store.";
+        }
+    }
+    return null;
+  }
+
+  static String getWarningMessage() {
+    String packageName = Parse.getApplicationContext().getPackageName();
+    String gcmPackagePermission = packageName + ".permission.C2D_MESSAGE";
+    return "make sure that these permissions are declared as children " +
+              "of the root <manifest> element:\n" +
+              "\n" +
+              "<uses-permission android:name=\"android.permission.INTERNET\" />\n" +
+              "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n" +
+              "<uses-permission android:name=\"android.permission.VIBRATE\" />\n" +
+              "<uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n" +
+              "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />\n" +
+              "<uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />\n" +
+              "<permission android:name=\"" + gcmPackagePermission + "\" " +
+              "android:protectionLevel=\"signature\" />\n" +
+              "<uses-permission android:name=\"" + gcmPackagePermission + "\" />\n" +
+              "\n" +
+              "Also, please make sure that these services and broadcast receivers are declared as " +
+              "children of the <application> element:\n" +
+              "\n" +
+              "<service android:name=\"com.parse.PushService\" />\n" +
+              "<receiver android:name=\"com.parse.GcmBroadcastReceiver\" " +
+              "android:permission=\"com.google.android.c2dm.permission.SEND\">\n" +
+              "  <intent-filter>\n" +
+              "    <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n" +
+              "    <action android:name=\"com.google.android.c2dm.intent.REGISTRATION\" />\n" +
+              "    <category android:name=\"" + packageName + "\" />\n" +
+              "  </intent-filter>\n" +
+              "</receiver>\n" +
+              "<receiver android:name=\"com.parse.ParsePushBroadcastReceiver\"" +
+              " android:exported=false>\n" +
+              "  <intent-filter>\n" +
+              "    <action android:name=\"com.parse.push.intent.RECEIVE\" />\n" +
+              "    <action android:name=\"com.parse.push.intent.OPEN\" />\n" +
+              "    <action android:name=\"com.parse.push.intent.DELETE\" />\n" +
+              "  </intent-filter>\n" +
+              "</receiver>";
+  }
+
+    @Override
+  public Task<Void> initialize() {
+    return GcmRegistrar.getInstance().registerAsync();
+  }
+
+  @WorkerThread
+  @Override
+  public void handlePush(Intent intent) {
+    if (intent != null) {
+      String action = intent.getAction();
+      if (REGISTER_RESPONSE_ACTION.equals(action)) {
+        handleGcmRegistrationIntent(intent);
+      } else if (RECEIVE_PUSH_ACTION.equals(action)) {
+        handleGcmPushIntent(intent);
+      } else {
+        PLog.e(TAG, "PushService got unknown intent in GCM mode: " + intent);
+      }
+    }
+  }
+
+  @WorkerThread
+  private void handleGcmRegistrationIntent(Intent intent) {
+    try {
+      // Have to block here since we are already in a background thread and as soon as we return,
+      // PushService may exit.
+      GcmRegistrar.getInstance().handleRegistrationIntentAsync(intent).waitForCompletion();
+    } catch (InterruptedException e) {
+      // do nothing
+    }
+  }
+
+  @WorkerThread
+  private void handleGcmPushIntent(Intent intent) {
+    String messageType = intent.getStringExtra("message_type");
+    if (messageType != null) {
+      /*
+       * The GCM docs reserve the right to use the message_type field for new actions, but haven't
+       * documented what those new actions are yet. For forwards compatibility, ignore anything
+       * with a message_type field.
+       */
+      PLog.i(TAG, "Ignored special message type " + messageType + " from GCM via intent " + intent);
+    } else {
+      String pushId = intent.getStringExtra("push_id");
+      String timestamp = intent.getStringExtra("time");
+      String dataString = intent.getStringExtra("data");
+      String channel = intent.getStringExtra("channel");
+
+      JSONObject data = null;
+      if (dataString != null) {
+        try {
+          data = new JSONObject(dataString);
+        } catch (JSONException e) {
+          PLog.e(TAG, "Ignoring push because of JSON exception while processing: " + dataString, e);
+          return;
+        }
+      }
+
+      PushRouter.getInstance().handlePush(pushId, timestamp, channel, data);
+    }
+  }
+
+}

+ 405 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GcmRegistrar.java

@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import bolts.Continuation;
+import bolts.Task;
+import bolts.TaskCompletionSource;
+
+/**
+ * A class that manages registering for GCM and updating the registration if it is out of date,
+ * used by {@link com.parse.GcmPushHandler}.
+ */
+/** package */ class GcmRegistrar {
+  private static final String TAG = "com.parse.GcmRegistrar";
+  private static final String REGISTRATION_ID_EXTRA = "registration_id";
+  private static final String ERROR_EXTRA = "error";
+
+  private static final String SENDER_ID_EXTRA = "com.parse.push.gcm_sender_id";
+
+  public static final String REGISTER_ACTION = "com.google.android.c2dm.intent.REGISTER";
+
+  private static final String FILENAME_DEVICE_TOKEN_LAST_MODIFIED = "deviceTokenLastModified";
+  private long localDeviceTokenLastModified;
+  private final Object localDeviceTokenLastModifiedMutex = new Object();
+
+  public static GcmRegistrar getInstance() {
+    return Singleton.INSTANCE;
+  }
+
+  private static class Singleton {
+    public static final GcmRegistrar INSTANCE = new GcmRegistrar(Parse.getApplicationContext());
+  }
+
+  private static String actualSenderIDFromExtra(Object senderIDExtra) {
+    if (!(senderIDExtra instanceof String)) {
+      return null;
+    }
+
+    String senderID = (String)senderIDExtra;
+    if (!senderID.startsWith("id:")) {
+      return null;
+    }
+
+    return senderID.substring(3);
+  }
+
+  private final Object lock = new Object();
+  private Request request = null;
+  private Context context = null;
+
+  // This a package-level constructor for unit testing only. Otherwise, use getInstance().
+  /* package */ GcmRegistrar(Context context) {
+    this.context = context;
+  }
+
+  /**
+   * Does nothing if the client already has a valid GCM registration id. Otherwise, sends out a
+   * GCM registration request and saves the resulting registration id to the server via
+   * ParseInstallation.
+   */
+  public Task<Void> registerAsync() {
+    if (ManifestInfo.getPushType() != PushType.GCM) {
+      return Task.forResult(null);
+    }
+    synchronized (lock) {
+      /*
+       * If we don't yet have a device token, mark this installation as wanting to use GCM by
+       * setting its pushType to GCM. If the registration does not succeed (because the device
+       * is offline, for instance), then update() will re-register for a GCM device token at
+       * next app initialize time.
+       */
+      final ParseInstallation installation = ParseInstallation.getCurrentInstallation();
+      // Check whether we need to send registration request, if installation does not
+      // have device token or local device token is stale, we need to send request.
+      Task<Boolean> checkTask = installation.getDeviceToken() == null
+          ? Task.forResult(true)
+          : isLocalDeviceTokenStaleAsync();
+      return checkTask.onSuccessTask(new Continuation<Boolean, Task<Void>>() {
+        @Override
+        public Task<Void> then(Task<Boolean> task) throws Exception {
+          if (!task.getResult()) {
+            return Task.forResult(null);
+          }
+          if (installation.getPushType() != PushType.GCM) {
+            installation.setPushType(PushType.GCM);
+          }
+          // We do not need to wait sendRegistrationRequestAsync, since this task will finish
+          // after we get the response from GCM, if we wait for this task, it will block our test.
+          sendRegistrationRequestAsync();
+          return Task.forResult(null);
+        }
+      });
+    }
+  }
+
+  private Task<Void> sendRegistrationRequestAsync() {
+    synchronized (lock) {
+      if (request != null) {
+        return Task.forResult(null);
+      }
+      // Look for an element like this as a child of the <application> element:
+      //
+      //   <meta-data android:name="com.parse.push.gcm_sender_id"
+      //              android:value="id:567327206255" />
+      //
+      // The reason why the "id:" prefix is necessary is because Android treats any metadata value
+      // that is a string of digits as an integer. So the call to Bundle.getString() will actually
+      // return null for `android:value="567327206255"`. Additionally, Bundle.getInteger() returns
+      // a 32-bit integer. For `android:value="567327206255"`, this returns a truncated integer
+      // because 567327206255 is larger than the largest 32-bit integer.
+      Bundle metaData = ManifestInfo.getApplicationMetadata(context);
+      String senderID = null;
+
+      if (metaData != null) {
+        Object senderIDExtra = metaData.get(SENDER_ID_EXTRA);
+
+        if (senderIDExtra != null) {
+          senderID = actualSenderIDFromExtra(senderIDExtra);
+
+          if (senderID == null) {
+            PLog.e(TAG, "Found " + SENDER_ID_EXTRA + " <meta-data> element with value \"" +
+                senderIDExtra.toString() + "\", but the value is missing the expected \"id:\" " +
+                "prefix.");
+            return null;
+          }
+        }
+      }
+
+      if (senderID == null) {
+          PLog.e(TAG, "You must provide " + SENDER_ID_EXTRA + " in your AndroidManifest.xml\n" +
+                  "Make sure to prefix with the value with id:\n\n" +
+                  "<meta-data\n" +
+                  "    android:name=\"com.parse.push.gcm_sender_id\"\n" +
+                  "    android:value=\"id:<YOUR_GCM_SENDER_ID>\" />");
+        return null;
+      }
+
+      request = Request.createAndSend(context, senderID);
+      return request.getTask().continueWith(new Continuation<String, Void>() {
+        @Override
+        public Void then(Task<String> task) {
+          Exception e = task.getError();
+          if (e != null) {
+            PLog.e(TAG, "Got error when trying to register for GCM push", e);
+          }
+
+          synchronized (lock) {
+            request = null;
+          }
+
+          return null;
+        }
+      });
+    }
+  }
+
+  /**
+   * Should be called by a broadcast receiver or service to handle the GCM registration response
+   * intent (com.google.android.c2dm.intent.REGISTRATION).
+   */
+  Task<Void> handleRegistrationIntentAsync(Intent intent) {
+    List<Task<Void>> tasks = new ArrayList<>();
+    /*
+     * We have to parse the response here because GCM may send us a new registration_id
+     * out-of-band without a request in flight.
+     */
+    String registrationId = intent.getStringExtra(REGISTRATION_ID_EXTRA);
+
+    if (registrationId != null && registrationId.length() > 0) {
+      PLog.v(TAG, "Received deviceToken <" + registrationId + "> from GCM.");
+
+      ParseInstallation installation = ParseInstallation.getCurrentInstallation();
+      // Compare the new deviceToken with the old deviceToken, we only update the
+      // deviceToken if the new one is different from the old one. This does not follow google
+      // guide strictly. But we find most of the time if user just update the app, the
+      // registrationId does not change so there is no need to save it again.
+      if (!registrationId.equals(installation.getDeviceToken())) {
+        installation.setPushType(PushType.GCM);
+        installation.setDeviceToken(registrationId);
+        tasks.add(installation.saveInBackground());
+      }
+      // We need to update the last modified even the deviceToken is the same. Otherwise when the
+      // app is opened again, isDeviceTokenStale() will always return false so we will send
+      // request to GCM every time.
+      tasks.add(updateLocalDeviceTokenLastModifiedAsync());
+    }
+    synchronized (lock) {
+      if (request != null) {
+        request.onReceiveResponseIntent(intent);
+      }
+    }
+    return Task.whenAll(tasks);
+  }
+
+  // Only used by tests.
+  /* package */ int getRequestIdentifier() {
+    synchronized (lock) {
+      return request != null ? request.identifier : 0;
+    }
+  }
+
+  /** package for tests */ Task<Boolean> isLocalDeviceTokenStaleAsync() {
+    return getLocalDeviceTokenLastModifiedAsync().onSuccessTask(new Continuation<Long, Task<Boolean>>() {
+      @Override
+      public Task<Boolean> then(Task<Long> task) throws Exception {
+        long localDeviceTokenLastModified = task.getResult();
+        return Task.forResult(localDeviceTokenLastModified != ManifestInfo.getLastModified());
+      }
+    });
+  }
+
+  /** package for tests */ Task<Void> updateLocalDeviceTokenLastModifiedAsync() {
+    return Task.call(new Callable<Void>() {
+      @Override
+      public Void call() throws Exception {
+        synchronized (localDeviceTokenLastModifiedMutex) {
+          localDeviceTokenLastModified = ManifestInfo.getLastModified();
+          final String localDeviceTokenLastModifiedStr =
+              String.valueOf(localDeviceTokenLastModified);
+          try {
+            ParseFileUtils.writeStringToFile(getLocalDeviceTokenLastModifiedFile(),
+                localDeviceTokenLastModifiedStr, "UTF-8");
+          } catch (IOException e) {
+            // do nothing
+          }
+        }
+        return null;
+      }
+    }, Task.BACKGROUND_EXECUTOR);
+  }
+
+  private Task<Long> getLocalDeviceTokenLastModifiedAsync() {
+    return Task.call(new Callable<Long>() {
+      @Override
+      public Long call() throws Exception {
+        synchronized (localDeviceTokenLastModifiedMutex) {
+          if (localDeviceTokenLastModified == 0) {
+            try {
+              String localDeviceTokenLastModifiedStr = ParseFileUtils.readFileToString(
+                  getLocalDeviceTokenLastModifiedFile(), "UTF-8");
+              localDeviceTokenLastModified = Long.valueOf(localDeviceTokenLastModifiedStr);
+            } catch (IOException e) {
+              localDeviceTokenLastModified = 0;
+            }
+          }
+          return localDeviceTokenLastModified;
+        }
+      }
+    }, Task.BACKGROUND_EXECUTOR);
+  }
+
+  /** package for tests */ static File getLocalDeviceTokenLastModifiedFile() {
+    File dir = Parse.getParseCacheDir("GCMRegistrar");
+    return new File(dir, FILENAME_DEVICE_TOKEN_LAST_MODIFIED);
+  }
+
+  /** package for tests */ static void deleteLocalDeviceTokenLastModifiedFile() {
+    ParseFileUtils.deleteQuietly(getLocalDeviceTokenLastModifiedFile());
+  }
+
+  /**
+   * Encapsulates the a GCM registration request-response, potentially using AlarmManager to
+   * schedule retries if the GCM service is not available.
+   */
+  private static class Request {
+    private static final String RETRY_ACTION = "com.parse.RetryGcmRegistration";
+    private static final int MAX_RETRIES = 5;
+    private static final int BACKOFF_INTERVAL_MS = 3000;
+
+    final private Context context;
+    final private String senderId;
+    final private Random random;
+    final private int identifier;
+    final private TaskCompletionSource<String> tcs;
+    final private PendingIntent appIntent;
+    final private AtomicInteger tries;
+    final private PendingIntent retryIntent;
+    final private BroadcastReceiver retryReceiver;
+
+    public static Request createAndSend(Context context, String senderId) {
+      Request request = new Request(context, senderId);
+      request.send();
+
+      return request;
+    }
+
+    private Request(Context context, String senderId) {
+      this.context = context;
+      this.senderId = senderId;
+      this.random = new Random();
+      this.identifier = this.random.nextInt();
+      this.tcs = new TaskCompletionSource<>();
+      this.appIntent = PendingIntent.getBroadcast(this.context, identifier, new Intent(), 0);
+      this.tries = new AtomicInteger(0);
+
+      String packageName = this.context.getPackageName();
+      Intent intent = new Intent(RETRY_ACTION).setPackage(packageName);
+      intent.addCategory(packageName);
+      intent.putExtra("random", identifier);
+      this.retryIntent = PendingIntent.getBroadcast(this.context, identifier, intent, 0);
+
+      this.retryReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+          if (intent != null && intent.getIntExtra("random", 0) == identifier) {
+            send();
+          }
+        }
+      };
+
+      IntentFilter filter = new IntentFilter();
+      filter.addAction(RETRY_ACTION);
+      filter.addCategory(packageName);
+
+      context.registerReceiver(this.retryReceiver, filter);
+    }
+
+    public Task<String> getTask() {
+      return tcs.getTask();
+    }
+
+    private void send() {
+      Intent intent = new Intent(REGISTER_ACTION);
+      intent.setPackage("com.google.android.gsf");
+      intent.putExtra("sender", senderId);
+      intent.putExtra("app", appIntent);
+
+      ComponentName name = null;
+      try {
+        name = context.startService(intent);
+      } catch (SecurityException exception) {
+        // do nothing
+      }
+
+      if (name == null) {
+        finish(null, "GSF_PACKAGE_NOT_AVAILABLE");
+      }
+
+      tries.incrementAndGet();
+
+      PLog.v(TAG, "Sending GCM registration intent");
+    }
+
+    public void onReceiveResponseIntent(Intent intent) {
+      String registrationId = intent.getStringExtra(REGISTRATION_ID_EXTRA);
+      String error = intent.getStringExtra(ERROR_EXTRA);
+
+      if (registrationId == null && error == null) {
+        PLog.e(TAG, "Got no registration info in GCM onReceiveResponseIntent");
+        return;
+      }
+
+      // Retry with exponential backoff if GCM isn't available.
+      if ("SERVICE_NOT_AVAILABLE".equals(error) && tries.get() < MAX_RETRIES) {
+        AlarmManager manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
+        int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
+        long delay = (1 << tries.get()) * BACKOFF_INTERVAL_MS + random.nextInt(BACKOFF_INTERVAL_MS);
+        long start = SystemClock.elapsedRealtime() + delay;
+        manager.set(alarmType, start, retryIntent);
+      } else {
+        finish(registrationId, error);
+      }
+    }
+
+    private void finish(String registrationId, String error) {
+      boolean didSetResult;
+
+      if (registrationId != null) {
+        didSetResult = tcs.trySetResult(registrationId);
+      } else {
+        didSetResult = tcs.trySetError(new Exception("GCM registration error: " + error));
+      }
+
+      if (didSetResult) {
+        appIntent.cancel();
+        retryIntent.cancel();
+        context.unregisterReceiver(this.retryReceiver);
+      }
+    }
+  }
+}

+ 47 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetCallback.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code GetCallback} is used to run code after a {@link ParseQuery} is used to fetch a
+ * {@link ParseObject} in a background thread.
+ * <p/>
+ * The easiest way to use a {@code GetCallback} is through an anonymous inner class. Override the
+ * {@code done} function to specify what the callback should do after the fetch is complete.
+ * The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * For example, this sample code fetches an object of class {@code "MyClass"} and id
+ * {@code myId}. It calls a different function depending on whether the fetch succeeded or not.
+ * <p/>
+ * <pre>
+ * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery(&quot;MyClass&quot;);
+ * query.getInBackground(myId, new GetCallback&lt;ParseObject&gt;() {
+ *   public void done(ParseObject object, ParseException e) {
+ *     if (e == null) {
+ *       objectWasRetrievedSuccessfully(object);
+ *     } else {
+ *       objectRetrievalFailed();
+ *     }
+ *   }
+ * });
+ * </pre>
+ */
+public interface GetCallback<T extends ParseObject> extends ParseCallback2<T, ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   * 
+   * @param object
+   *          The object that was retrieved, or {@code null} if it did not succeed.
+   * @param e
+   *          The exception raised by the fetch, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(T object, ParseException e);
+}

+ 40 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetDataCallback.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+/**
+ * A {@code GetDataCallback} is used to run code after a {@link ParseFile} fetches its data on a
+ * background thread.
+ * <p/>
+ * The easiest way to use a {@code GetDataCallback} is through an anonymous inner class. Override
+ * the {@code done} function to specify what the callback should do after the fetch is complete.
+ * The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * <pre>
+ * file.getDataInBackground(new GetDataCallback() {
+ *   public void done(byte[] data, ParseException e) {
+ *     // ...
+ *   }
+ * });
+ * </pre>
+ */
+public interface GetDataCallback extends ParseCallback2<byte[], ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   *
+   * @param data
+   *          The data that was retrieved, or {@code null} if it did not succeed.
+   * @param e
+   *          The exception raised by the fetch, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(byte[] data, ParseException e);
+}
+

+ 41 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetDataStreamCallback.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.io.InputStream;
+
+/**
+ * A {@code GetDataStreamCallback} is used to run code after a {@link ParseFile} fetches its data on
+ * a background thread.
+ * <p/>
+ * The easiest way to use a {@code GetDataStreamCallback} is through an anonymous inner class.
+ * Override the {@code done} function to specify what the callback should do after the fetch is
+ * complete. The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * <pre>
+ * file.getDataStreamInBackground(new GetDataStreamCallback() {
+ *   public void done(InputSteam input, ParseException e) {
+ *     // ...
+ *   }
+ * });
+ * </pre>
+ */
+public interface GetDataStreamCallback extends ParseCallback2<InputStream, ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   *
+   * @param input
+   *          The data that was retrieved, or {@code null} if it did not succeed.
+   * @param e
+   *          The exception raised by the fetch, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(InputStream input, ParseException e);
+}

+ 41 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/GetFileCallback.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.io.File;
+
+/**
+ * A {@code GetFileCallback} is used to run code after a {@link ParseFile} fetches its data on
+ * a background thread.
+ * <p/>
+ * The easiest way to use a {@code GetFileCallback} is through an anonymous inner class.
+ * Override the {@code done} function to specify what the callback should do after the fetch is 
+ * complete. The {@code done} function will be run in the UI thread, while the fetch happens in a
+ * background thread. This ensures that the UI does not freeze while the fetch happens.
+ * <p/>
+ * <pre>
+ * file.getFileInBackground(new GetFileCallback() {
+ *   public void done(File file, ParseException e) {
+ *     // ...
+ *   }
+ * });
+ * </pre>
+ */
+public interface GetFileCallback extends ParseCallback2<File, ParseException> {
+  /**
+   * Override this function with the code you want to run after the fetch is complete.
+   *
+   * @param file
+   *          The data that was retrieved, or {@code null} if it did not succeed.
+   * @param e
+   *          The exception raised by the fetch, or {@code null} if it succeeded.
+   */
+  @Override
+  void done(File file, ParseException e);
+}

+ 90 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/InstallationId.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+package com.parse;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Since we cannot save dirty ParseObjects to disk and we must be able to persist UUIDs across
+ * restarts even if the ParseInstallation is not saved, we use this legacy file still as a
+ * boostrapping environment as well until the full ParseInstallation is cached to disk.
+ *
+ * TODO: Allow dirty objects to be saved to disk.
+ */
+/* package */ class InstallationId {
+
+  private static final String TAG = "InstallationId";
+
+  private final Object lock = new Object();
+  private final File file;
+  private String installationId;
+
+  public InstallationId(File file) {
+    this.file = file;
+  }
+
+  /**
+   * Loads the installationId from memory, then tries to loads the legacy installationId from disk
+   * if it is present, or creates a new random UUID.
+   */
+  public String get() {
+    synchronized (lock) {
+      if (installationId == null) {
+        try {
+          installationId = ParseFileUtils.readFileToString(file, "UTF-8");
+        } catch (FileNotFoundException e) {
+          PLog.i(TAG, "Couldn't find existing installationId file. Creating one instead.");
+        } catch (IOException e) {
+          PLog.e(TAG, "Unexpected exception reading installation id from disk", e);
+        }
+      }
+
+      if (installationId == null) {
+        setInternal(UUID.randomUUID().toString());
+      }
+    }
+
+    return installationId;
+  }
+
+  /**
+   * Sets the installationId and persists it to disk.
+   */
+  public void set(String newInstallationId) {
+    synchronized (lock) {
+      if (ParseTextUtils.isEmpty(newInstallationId)
+        || newInstallationId.equals(get())) {
+        return;
+      }
+      setInternal(newInstallationId);
+    }
+  }
+
+  private void setInternal(String newInstallationId) {
+    synchronized (lock) {
+      try {
+        ParseFileUtils.writeStringToFile(file, newInstallationId, "UTF-8");
+      } catch (IOException e) {
+        PLog.e(TAG, "Unexpected exception writing installation id to disk", e);
+      }
+
+      installationId = newInstallationId;
+    }
+  }
+
+  /* package for tests */ void clear() {
+    synchronized (lock) {
+      installationId = null;
+      ParseFileUtils.deleteQuietly(file);
+    }
+  }
+}

+ 36 - 0
ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/KnownParseObjectDecoder.java