master f422166d2f14 cached
109 files
479.0 KB
111.6k tokens
406 symbols
1 requests
Download .txt
Showing preview only (514K chars total). Download the full file or copy to clipboard to get everything.
Repository: googlesamples/android-play-games-in-motion
Branch: master
Commit: f422166d2f14
Files: 109
Total size: 479.0 KB

Directory structure:
gitextract_6xtmc2to/

├── .gitignore
├── .gitmodules
├── .idea/
│   ├── .name
│   ├── compiler.xml
│   ├── copyright/
│   │   ├── Apache_2_0.xml
│   │   └── profiles_settings.xml
│   ├── encodings.xml
│   ├── gradle.xml
│   ├── libraries/
│   │   ├── appcompat_v7_21_0_3.xml
│   │   ├── junit_3_8.xml
│   │   ├── play_services_6_1_71.xml
│   │   ├── support_annotations_21_0_3.xml
│   │   └── support_v4_21_0_3.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── scopes/
│   │   └── scope_settings.xml
│   └── vcs.xml
├── CONTRIBUTING
├── ExampleGame.iml
├── LICENSE
├── app/
│   ├── app.iml
│   ├── build.gradle
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── fpl/
│       │                   └── gim/
│       │                       └── examplegame/
│       │                           └── MissionParseTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   ├── legacy_missions/
│           │   │   ├── choice_mission.xml
│           │   │   ├── mission.xml
│           │   │   ├── sfx mission.xml
│           │   │   ├── spoken_plus_timer_mission.xml
│           │   │   ├── texttospeechmission.xml
│           │   │   └── timermission.xml
│           │   └── missions/
│           │       ├── 01_sample_mission_1.xml
│           │       ├── 02_sample_mission_2.xml
│           │       ├── ex_01_timer_moment.xml
│           │       ├── ex_02_spoken_text_moment.xml
│           │       ├── ex_03_choice_moment.xml
│           │       ├── ex_04_sfx_moment.xml
│           │       ├── ex_05_broken_timer_moment.xml
│           │       └── ex_06_long_timer_mission.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── fpl/
│           │               └── gim/
│           │                   └── examplegame/
│           │                       ├── Choice.java
│           │                       ├── ChoiceMoment.java
│           │                       ├── ChoiceMomentData.java
│           │                       ├── MainActivity.java
│           │                       ├── MainService.java
│           │                       ├── Mission.java
│           │                       ├── MissionData.java
│           │                       ├── Moment.java
│           │                       ├── MomentData.java
│           │                       ├── Outcome.java
│           │                       ├── SfxMoment.java
│           │                       ├── SfxMomentData.java
│           │                       ├── SpokenTextMoment.java
│           │                       ├── SpokenTextMomentData.java
│           │                       ├── TimerMoment.java
│           │                       ├── TimerMomentData.java
│           │                       ├── google/
│           │                       │   ├── FitDataTypeSetting.java
│           │                       │   ├── FitResultCallback.java
│           │                       │   └── GoogleApiClientWrapper.java
│           │                       ├── gui/
│           │                       │   ├── EndSummaryFragment.java
│           │                       │   ├── FitnessDataDisplayFragment.java
│           │                       │   ├── GameViews.java
│           │                       │   ├── MissionSelectionFragment.java
│           │                       │   ├── MusicSelectionFragment.java
│           │                       │   ├── NotificationOptions.java
│           │                       │   ├── RunSpecificationSelectionFragment.java
│           │                       │   └── StartMenuFragment.java
│           │                       └── utils/
│           │                           ├── MissionParseException.java
│           │                           ├── MissionParser.java
│           │                           └── Utils.java
│           └── res/
│               ├── anim/
│               │   ├── slide_in_right.xml
│               │   └── slide_out_left.xml
│               ├── drawable/
│               │   └── weapon_charge_progress.xml
│               ├── layout/
│               │   ├── activity_main.xml
│               │   ├── end_screen.xml
│               │   ├── menu_list_item.xml
│               │   ├── menu_mission_list.xml
│               │   ├── menu_music_selection.xml
│               │   ├── menu_run_specifications.xml
│               │   ├── menu_start.xml
│               │   ├── placeholder_fragment.xml
│               │   └── step_display.xml
│               ├── menu/
│               │   └── main.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── docs/
│   ├── generate_docs.py
│   └── src/
│       ├── contributing.md
│       ├── doxyfile
│       ├── doxygen_layout.xml
│       ├── index.md
│       └── programmers_guide/
│           ├── assets.md
│           ├── audio.md
│           ├── building.md
│           ├── core.md
│           ├── gameplay.md
│           ├── google_api.md
│           ├── mission.md
│           └── overview.md
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── prototype.iml
├── readme.md
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
#built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/
build/
app/build/

# Local configuration file (sdk path, etc)
local.properties

# Windows thumbnail db
Thumbs.db

# OSX files
.DS_Store

# Eclipse project files
.classpath
.project

# Android Studio project files
.idea/workspace.xml
.gradle


================================================
FILE: .gitmodules
================================================
[submodule "dependencies/fplutil"]
	path = dependencies/fplutil
	url = http://github.com/google/fplutil.git


================================================
FILE: .idea/.name
================================================
ExampleGame

================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <option name="DEFAULT_COMPILER" value="Javac" />
    <resourceExtensions />
    <wildcardResourcePatterns>
      <entry name="!?*.java" />
      <entry name="!?*.form" />
      <entry name="!?*.class" />
      <entry name="!?*.groovy" />
      <entry name="!?*.scala" />
      <entry name="!?*.flex" />
      <entry name="!?*.kt" />
      <entry name="!?*.clj" />
    </wildcardResourcePatterns>
    <annotationProcessing>
      <profile default="true" name="Default" enabled="false">
        <processorPath useClasspath="true" />
      </profile>
    </annotationProcessing>
  </component>
</project>



================================================
FILE: .idea/copyright/Apache_2_0.xml
================================================
<component name="CopyrightManager">
  <copyright>
    <option name="notice" value="Copyright 2015 Google Inc. All Rights Reserved.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;    http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
    <option name="keyword" value="Copyright" />
    <option name="allowReplaceKeyword" value="" />
    <option name="myName" value="Apache 2.0" />
    <option name="myLocal" value="true" />
  </copyright>
</component>

================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="Apache 2.0" />
</component>

================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>



================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="distributionType" value="LOCAL" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.2.1" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
      </GradleProjectSettings>
    </option>
  </component>
</project>



================================================
FILE: .idea/libraries/appcompat_v7_21_0_3.xml
================================================
<component name="libraryTable">
  <library name="appcompat-v7-21.0.3">
    <CLASSES>
      <root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/res" />
      <root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3/classes.jar!/" />
    </CLASSES>
    <JAVADOC />
    <SOURCES />
  </library>
</component>

================================================
FILE: .idea/libraries/junit_3_8.xml
================================================
<component name="libraryTable">
  <library name="junit-3.8">
    <CLASSES>
      <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/3.8/b4cd87d6abbc43078ffc915c7181e4c8961e5b7b/junit-3.8.jar!/" />
    </CLASSES>
    <JAVADOC />
    <SOURCES />
  </library>
</component>

================================================
FILE: .idea/libraries/play_services_6_1_71.xml
================================================
<component name="libraryTable">
  <library name="play-services-6.1.71">
    <CLASSES>
      <root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.google.android.gms/play-services/6.1.71/res" />
      <root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.google.android.gms/play-services/6.1.71/classes.jar!/" />
    </CLASSES>
    <JAVADOC />
    <SOURCES />
  </library>
</component>

================================================
FILE: .idea/libraries/support_annotations_21_0_3.xml
================================================
<component name="libraryTable">
  <library name="support-annotations-21.0.3">
    <CLASSES>
      <root url="jar://$USER_HOME$/projects/android-sdk/extras/android/m2repository/com/android/support/support-annotations/21.0.3/support-annotations-21.0.3.jar!/" />
    </CLASSES>
    <JAVADOC />
    <SOURCES>
      <root url="jar://$USER_HOME$/projects/android-sdk/extras/android/m2repository/com/android/support/support-annotations/21.0.3/support-annotations-21.0.3-sources.jar!/" />
    </SOURCES>
  </library>
</component>

================================================
FILE: .idea/libraries/support_v4_21_0_3.xml
================================================
<component name="libraryTable">
  <library name="support-v4-21.0.3">
    <CLASSES>
      <root url="file://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/res" />
      <root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/classes.jar!/" />
      <root url="jar://$PROJECT_DIR$/app/build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3/libs/internal_impl-21.0.3.jar!/" />
    </CLASSES>
    <JAVADOC />
    <SOURCES>
      <root url="jar://$USER_HOME$/projects/android-sdk/extras/android/m2repository/com/android/support/support-v4/21.0.3/support-v4-21.0.3-sources.jar!/" />
    </SOURCES>
  </library>
</component>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="EntryPointsManager">
    <entry_points version="2.0" />
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
</project>



================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/ExampleGame.iml" filepath="$PROJECT_DIR$/ExampleGame.iml" />
      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
    </modules>
  </component>
</project>



================================================
FILE: .idea/scopes/scope_settings.xml
================================================
<component name="DependencyValidationManager">
  <state>
    <option name="SKIP_IMPORT_STATEMENTS" value="false" />
  </state>
</component>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="" />
  </component>
</project>



================================================
FILE: CONTRIBUTING
================================================
Contributing    {#contributing}
============

Want to contribute? Great! First, read this page (including the small print at
the end).

# Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.

# Code reviews
All submissions, including submissions by project members, require review. We
use Github pull requests for this purpose.

# The small print
Contributions made by corporations are covered by a different agreement than
the one above, the Software Grant and Corporate Contributor License Agreement.


================================================
FILE: ExampleGame.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
  <component name="FacetManager">
    <facet type="java-gradle" name="Java-Gradle">
      <configuration>
        <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
      </configuration>
    </facet>
  </component>
  <component name="NewModuleRootManager" inherit-compiler-output="true">
    <exclude-output />
    <content url="file://$MODULE_DIR$">
      <excludeFolder url="file://$MODULE_DIR$/.gradle" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>



================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: app/app.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="ExampleGame" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
  <component name="FacetManager">
    <facet type="android-gradle" name="Android-Gradle">
      <configuration>
        <option name="GRADLE_PROJECT_PATH" value=":app" />
      </configuration>
    </facet>
    <facet type="android" name="Android">
      <configuration>
        <option name="SELECTED_BUILD_VARIANT" value="debug" />
        <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
        <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
        <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
        <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
        <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
        <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" />
        <option name="ALLOW_USER_CONFIGURATION" value="false" />
        <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
        <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
        <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
        <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
      </configuration>
    </facet>
  </component>
  <component name="NewModuleRootManager" inherit-compiler-output="false">
    <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
    <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
    <exclude-output />
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" />
      <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/test/debug" type="java-test-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
      <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
      <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
      <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
    </content>
    <orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
    <orderEntry type="sourceFolder" forTests="false" />
    <orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
    <orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />
    <orderEntry type="library" exported="" name="support-v4-21.0.3" level="project" />
    <orderEntry type="library" exported="" name="junit-3.8" level="project" />
    <orderEntry type="library" exported="" name="play-services-6.1.71" level="project" />
  </component>
</module>



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

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "19.1"

    defaultConfig {
        applicationId "com.google.fpl.gim.examplegame"
        minSdkVersion 16
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
    }
}

repositories {
    mavenCentral()
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    compile group: 'junit', name: 'junit', version: '3.8'
    compile 'com.android.support:appcompat-v7:21.+'
    compile 'com.google.android.gms:play-services:6.1.+'
}

================================================
FILE: app/src/androidTest/java/com/google/fpl/gim/examplegame/MissionParseTest.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import com.google.fpl.gim.examplegame.utils.MissionParseException;
import com.google.fpl.gim.examplegame.utils.MissionParser;
import com.google.fpl.gim.examplegame.utils.Utils;

import junit.framework.Assert;
import junit.framework.TestCase;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
 * Tests the functionality of mission parsing from XML.
 */
public class MissionParseTest extends TestCase {

    private static final String TAG = MissionParseTest.class.getSimpleName();

    private Mission mMission;
    private MissionData mMissionData;

    public void setUp() {
        Utils.logDebug(TAG, "Setting up...");
        // Set up mission data that will be used for every test case
        String missionId = "Mission 1";
        float lengthOfGameMinutes = 30f; // 30-minute game
        float lengthOfIntervalMinutes = 1f; // 1-minute intervals
        float challengePaceMinutesPerMile = 8.0f;
        mMissionData = new MissionData(missionId, missionId, lengthOfGameMinutes,
                lengthOfIntervalMinutes, challengePaceMinutesPerMile);
    }

    public void tearDown() {
        Utils.logDebug(TAG, "Tearing down...");
    }

    /**
     * Test for correct construction of a timer moment.
     */
    public void testTimerMomentConstruction() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createTimerMomentXml(                // <moment type="timer"> [...]
                "start",                            // Moment id.
                "start",                            // Next moment id.
                0.5);                               // Length of timer moment (minutes).
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(1, mMissionData.getNumMoments());
        Assert.assertEquals(true, mMissionData.getMomentFromId("start") instanceof TimerMoment);
        Assert.assertEquals("start", mMissionData.getMomentFromId("start").getNextMomentId());
    }

    /**
     * Test for correct construction of a choice moment.
     */
    public void testChoiceMomentConstruction() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createStartChoiceMomentXml(          // <moment type="choice"> [...]
                "start",                            // Moment id.
                0.5,                                // Length of choice timeout in minutes.
                "Example ChoiceMoment Description", // Description of choice moment.
                "choice_2");                        // Id of default choice.

        xml += createChoiceXml(                     // <choice> [...]
                "fire",                             // Choice id.
                "Example Choice Description 1",     // Description of choice.
                "start",                            // Next moment id.
                true,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createChoiceXml(                     // <choice> [...]
                "choice_2",                         // Choice id.
                "Example Choice Description 2",     // Description of choice.
                "start",                            // Next moment id.
                false,                              // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createChoiceXml(                     // <choice> [...]
                "choice_3",                         // Choice id.
                "Example Choice Description 3",     // Description of choice.
                "start",                            // Next moment id.
                true,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createEndChoiceMomentXml();          // </moment>

        xml += createEndMissionXml();               // </mission>

        Utils.logDebug(TAG, xml);

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(1, mMissionData.getNumMoments());
        Assert.assertEquals(true, mMissionData.getMomentFromId("start") instanceof ChoiceMoment);

        ChoiceMoment choiceMoment = ((ChoiceMoment) mMissionData.getMomentFromId("start"));
        Assert.assertEquals(3, choiceMoment.getMomentData().getNumChoices());

        Choice choice1 = choiceMoment.getMomentData().getChoiceById("choice_2");
        Assert.assertEquals("Example Choice Description 2", choice1.getDescription());
        Assert.assertEquals("start", choice1.getNextMomentId());
        Assert.assertEquals(false, choice1.getOutcome().weaponChargeDepleted());
        Assert.assertEquals(false, choice1.getOutcome().numEnemiesDefeatedIncremented());
        Assert.assertEquals("test_icon", choice1.getDrawableResourceName());
        Assert.assertEquals(null, mMissionData.getMomentFromId("start").getNextMomentId());
    }

    /**
     * Test for correct construction of an sfx moment.
     */
    public void testSfxMomentConstruction() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createSfxMomentXml(                  // <moment type="sfx"> [...]
                "start",                            // Moment id.
                "start",                            // Next moment id.
                "path/to/something");               // Path to sound effect resource.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(1, mMissionData.getNumMoments());
        Assert.assertEquals(true, mMissionData.getMomentFromId("start") instanceof SfxMoment);
        Assert.assertEquals("start", mMissionData.getMomentFromId("start").getNextMomentId());
    }

    /**
     * Test for correct construction of a spoken text moment.
     */
    public void testSpokenTextMomentConstruction() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createSpokenTextMomentXml(           // <moment type="spoken_text"> [...]
                "start",                            // Moment id.
                "start",                            // Next moment id.
                "Hello!");                          // Text to speak out loud.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(1, mMissionData.getNumMoments());
        Assert.assertEquals(true,
                mMissionData.getMomentFromId("start") instanceof SpokenTextMoment);
        Assert.assertEquals("start", mMissionData.getMomentFromId("start").getNextMomentId());
    }

    /**
     * Test for correct construction of a mission with four different types of moments.
     */
    public void testFourMomentConstruction() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createSpokenTextMomentXml(           // <moment type="spoken_text"> [...]
                "start",                            // Moment id.
                "second",                           // Next moment id.
                "Hello!");                          // Text to speak out loud.
                                                    // </moment>

        xml += createSfxMomentXml(                  // <moment type="sfx"> [...]
                "second",                           // Moment id.
                "third",                            // Next moment id.
                "path/to/something");               // Path to sound effect resource.
                                                    // </moment>

        xml += createStartChoiceMomentXml(          // <moment type="choice"> [...]
                "third",                            // Moment id.
                0.5,                                // Length of choice timeout in minutes.
                "Example ChoiceMoment Description", // Description of choice moment.
                "choice_2");                        // Id of default choice.
        xml += createChoiceXml(                     // <choice> [...]
                "fire",                             // Choice id.
                "Example Choice Description 1",     // Description of choice.
                "fourth",                            // Next moment id.
                true,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>
        xml += createChoiceXml(                     // <choice> [...]
                "choice_2",                         // Choice id.
                "Example Choice Description 2",     // Description of choice.
                "fourth",                           // Next moment id.
                false,                              // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>
        xml += createEndChoiceMomentXml();          // </moment>

        xml += createTimerMomentXml(                // <moment type="timer"> [...]
                "fourth",                           // Moment id.
                null,                               // Next moment id.
                0.25);                              // Length of timer moment (minutes).
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(4, mMissionData.getNumMoments());

        Moment spokenTextMoment = mMissionData.getMomentFromId("start");
        Moment sfxMoment = mMissionData.getMomentFromId("second");
        Moment choiceMoment = mMissionData.getMomentFromId("third");
        Moment timerMoment = mMissionData.getMomentFromId("fourth");

        Assert.assertEquals(true, spokenTextMoment instanceof SpokenTextMoment);
        Assert.assertEquals(true, sfxMoment instanceof SfxMoment);
        Assert.assertEquals(true, choiceMoment instanceof ChoiceMoment);
        Assert.assertEquals(true, timerMoment instanceof TimerMoment);

        SpokenTextMoment moment1 = (SpokenTextMoment) spokenTextMoment;
        SfxMoment moment2 = (SfxMoment) sfxMoment;
        ChoiceMoment moment3 = (ChoiceMoment) choiceMoment;
        TimerMoment moment4 = (TimerMoment) timerMoment;

        Assert.assertEquals("second", moment1.getNextMomentId());
        Assert.assertEquals("third", moment2.getNextMomentId());
        // The next ID of a ChoiceMoment is null until the user has made their choice.
        Assert.assertEquals(null, moment3.getNextMomentId());
        Assert.assertEquals("fourth", moment3.getMomentData().getChoices()[0].getNextMomentId());
        // The final moment has a 'null' next moment.
        Assert.assertEquals(null, moment4.getNextMomentId());

        Assert.assertEquals(true, moment3.getMomentData().getChoiceById("fire")
                .requiresChargedWeapon());
        Assert.assertEquals(false, moment3.getMomentData().getChoiceById("choice_2")
                .requiresChargedWeapon());
    }

    /**
     * Test for correct error handling with a moment has an invalid 'type'.
     */
    public void testMomentWithInvalidTypeErrorHandling() {
        String xml = "";

        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createStartMomentXml(                // <moment> [...]
                "invalid",                          // Moment type is invalid.
                "start");                           // Moment id.
        xml += createNextMomentXml("start");        // Next moment id.
        xml += createLengthMinutesXml(0.25);        // Length of (timer) moment in minutes.
        xml += createEndMomentXml();                // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        boolean didMissionParseFail = false;


        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            didMissionParseFail = true;
        }

        Assert.assertEquals(true, didMissionParseFail);
    }

    /**
     * Test for correct error handling with a moment has no 'type' attribute.
     */
    public void testMomentWithNoTypeErrorHandling() {
        String xml = "";

        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += "<moment " +                         // <moment> [...]
                "id='start' >";                     // Moment id. Moment type is missing.
        xml += createNextMomentXml("start");        // Next moment id.
        xml += createLengthMinutesXml(0.25);        // Length of (timer) moment in minutes.
        xml += createEndMomentXml();                // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        boolean didMissionParseFail = false;
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            didMissionParseFail = true;
        }

        Assert.assertEquals(true, didMissionParseFail);
    }

    /*
    * Test for correct behavior when a moment has no 'next_moment' attribute (signifies that this
    * moment is the final moment in the mission.)
    */
    public void testTimerMomentMissingNextMomentMission() {
        String xml = "";

        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Name of mission.

        xml += createStartMomentXml(                // <moment> [...]
                "timer",                            // Moment type.
                "start");                           // Moment id.
        xml += createLengthMinutesXml(0.25);        // Length of (timer) moment in minutes.

        xml += createEndMomentXml();                // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Mission 1", mMissionData.getMissionId());
        Assert.assertEquals(1, mMissionData.getNumMoments());
        Assert.assertEquals(true, mMissionData.getMomentFromId("start") instanceof TimerMoment);
        Assert.assertEquals(null, mMissionData.getMomentFromId("start").getNextMomentId());
    }

    /**
     * Test for correct reading of a mission name.
     */
    public void testMissionNameConstruction() {
        String xml = "";

        xml += createStartMissionXml(               // <mission> [...]
                "",                                 // First moment in mission.
                "Name");                            // Mission name.

        xml += createEndMissionXml();               // </mission>

        InputStream missionInputStream = new ByteArrayInputStream(xml.getBytes());
        String missionName = null;
        try {
            missionName = MissionParser.getMissionName(missionInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals("Name", missionName);
    }

    /**
     * Test for correct error handling for a mission with no "name" attribute.
     */
    public void testMissingMissionNameHandling() {
        String xml = "";

        xml += "<mission " +                        // <mission> [...]
                "start_id='' >";                    // First moment in mission. Mission name is
                                                    // missing.

        xml += createEndMissionXml();               // </mission>

        InputStream missionInputStream = new ByteArrayInputStream(xml.getBytes());
        boolean didMissionNameParseFail = false;
        try {
            MissionParser.getMissionName(missionInputStream);
        } catch (MissionParseException e) {
            didMissionNameParseFail = true;
        }

        Assert.assertEquals(true, didMissionNameParseFail);
    }

    /**
     * Test for correct parsing of fictional progress for a SpokenText moment.
     */
    public void testFictionalProgressSpokenTextMomentParsing() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createSpokenTextMomentWithFictionalProgressXML(
                                                    // <moment> [...]
                "start",                            // Moment id.
                null,                               // Next moment id.
                "Text to speak",                    // Text to speak out loud.
                "Fictional progress.");             // Fictional progress.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals(1, mMissionData.getMomentFromId("start").getFictionalProgress().size());
        Assert.assertEquals("Fictional progress.",
                mMissionData.getMomentFromId("start").getFictionalProgress().get(0));
    }

    /**
     * Test for correct parsing of fictional progress for a Timer moment.
     */
    public void testFictionalProgressTimerMomentParsing() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createTimerMomentWithFictionalProgressXML(
                                                    // <moment> [...]
                "start",                            // Moment id.
                null,                               // Next moment id.
                1.0,                                // Length of timer moment (minutes).
                "Fictional progress.");             // Fictional progress.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals(1, mMissionData.getMomentFromId("start").getFictionalProgress().size());
        Assert.assertEquals("Fictional progress.",
                mMissionData.getMomentFromId("start").getFictionalProgress().get(0));
    }

    /**
     * Test for correct parsing of fictional progress for an Sfx moment.
     */
    public void testFictionalProgressSfxMomentParsing() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createSfxMomentWithFictionalProgressXML(
                                                    // <moment> [...]
                "start",                            // Moment id.
                null,                               // Next moment id.
                "path/to/something",                // Path to sound effect resource.
                "Fictional progress.");             // Fictional progress.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        Assert.assertEquals(1, mMissionData.getMomentFromId("start").getFictionalProgress().size());
        Assert.assertEquals("Fictional progress.",
                mMissionData.getMomentFromId("start").getFictionalProgress().get(0));
    }

    /**
     * Test for correct parsing of fictional progress for a Choice moment, and for correct parsing
     * of fictional progress for Choices.
     */
    public void testFictionalProgressChoiceMomentParsing() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createStartChoiceMomentWithFictionalProgressXML(
                                                    // <moment> [...]
                "start",                            // Moment id.
                1.0,                                // Next moment id.
                "Choice Description",               // Description of choice moment.
                "choice_2",                         // Id of default choice.
                "Fictional progress.");             // Fictional progress.

        xml += createChoiceWithFictionalProgressXML(
                                                    // <choice> [...]
                "fire",                             // Choice id.
                "Example Choice Description 1",     // Description of choice.
                null,                               // Next moment id.
                true,                               // Whether the weapon charge should be depleted.
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "Description 1",                    // Fictional progress.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createChoiceXml(                     // <choice> [...]
                "choice_2",                         // Choice id.
                "Choice 2 description",             // Description of choice.
                null,                               // Next moment id.
                false,                              // Whether the weapon charge should be depleted.
                false,                              // Whether the number of enemies defeated should
                //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createEndMomentXml();                // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
        }

        ChoiceMoment choiceMoment = ((ChoiceMoment) mMissionData.getMomentFromId("start"));

        Choice fireChoice = choiceMoment.getMomentData().getChoiceById("fire");
        Assert.assertEquals(1, fireChoice.getFictionalProgress().size());
        Assert.assertEquals("Description 1", fireChoice.getFictionalProgress().get(0));

        Choice choice2 = choiceMoment.getMomentData().getChoiceById("choice_2");
        Assert.assertEquals(0, choice2.getFictionalProgress().size());

        Assert.assertEquals(1, mMissionData.getMomentFromId("start").getFictionalProgress().size());
        Assert.assertEquals("Fictional progress.",
                mMissionData.getMomentFromId("start").getFictionalProgress().get(0));
    }

    /**
     * Test for correct error handling for a fictional progress element that is empty.
     */
    public void testFictionalProgressErrorHandling() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createTimerMomentWithFictionalProgressXML(
                                                    // <moment> [...]
                "start",                            // Moment id.
                null,                               // Next moment id.
                1.0,                                // Length of timer moment (minutes).
                "");                                // Empty fictional progress.
                                                    // </moment>

        xml += createEndMissionXml();               // </mission>

        InputStream momentInputStream = new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        boolean didMissionParseFail = false;


        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            didMissionParseFail = true;
        }

        Assert.assertEquals(true, didMissionParseFail);
    }

    /**
     * Test for correct error handling for a Choice that has no icon element.
     */
    public void testChoiceMissingIconErrorHandling() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createStartChoiceMomentXml(          // <moment type="choice"> [...]
                "start",                            // Moment id.
                0.5,                                // Length of choice timeout in minutes.
                "Example ChoiceMoment Description", // Description of choice moment.
                "choice_2");                        // Id of default choice.

        xml += createChoiceXml(                     // <choice> [...]
                "fire",                             // Choice id.
                "Example Choice Description 1",     // Description of choice.
                "start",                            // Next moment id.
                true,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>


        xml += createChoiceXml(                     // <choice> [...]
                "choice_2",                         // Choice id.
                "Example Choice Description 2",     // Description of choice.
                "start",                            // Next moment id.
                false,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                null);                              // Missing icon element.
                                                    // </choice>

        xml += createEndChoiceMomentXml();          // </moment>

        xml += createEndMissionXml();               // </mission>

        Utils.logDebug(TAG, xml);

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        boolean didMissionParseFail = false;
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
            didMissionParseFail = true;
        }

        Assert.assertEquals(true, didMissionParseFail);
    }

    /**
     * Test for correct error handling for a Choice that has an empty icon element.
     */
    public void testChoiceEmptyIconErrorHandling() {
        String xml = "";
        xml += createStartMissionXml(               // <mission> [...]
                "start",                            // First moment in mission.
                "Name");                            // Mission name.

        xml += createStartChoiceMomentXml(          // <moment type="choice"> [...]
                "start",                            // Moment id.
                0.5,                                // Length of choice timeout in minutes.
                "Example ChoiceMoment Description", // Description of choice moment.
                "choice_2");                        // Id of default choice.

        xml += createChoiceXml(                     // <choice> [...]
                "fire",                             // Choice id.
                "Example Choice Description 1",     // Description of choice.
                "start",                            // Next moment id.
                true,                               // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "test_icon");                       // Icon resource name.
                                                    // </choice>

        xml += createChoiceXml(                     // <choice> [...]
                "choice_2",                             // Choice id.
                "Example Choice Description 2",     // Description of choice.
                "start",                            // Next moment id.
                false,                              // Whether the weapon charge should be depleted
                false,                              // Whether the number of enemies defeated should
                                                    //    be incremented.
                "");                                // Empty icon resource name.
                                                    // </choice>


        xml += createEndChoiceMomentXml();          // </moment>

        xml += createEndChoiceMomentXml();          // </moment>

        xml += createEndMissionXml();               // </mission>

        Utils.logDebug(TAG, xml);

        InputStream momentInputStream =
                new ByteArrayInputStream(xml.getBytes());
        mMission = new Mission(mMissionData);
        boolean didMissionParseFail = false;
        try {
            mMission.readMoments(momentInputStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
            didMissionParseFail = true;
        }

        Assert.assertEquals(true, didMissionParseFail);
    }

    /**
     * A helper function to create an XML string representing the start of a mission.
     * @param startMomentId The id of the first moment in the mission.
     * @param missionName The plain text name of the mission.
     * @return An XML string of the following format:
     *      <?xml version='1.0' encoding='utf-8'?>
     *      <mission start_id="[startMomentId]" name="[missionName]">
     */
    private String createStartMissionXml(String startMomentId, String missionName) {
        return "<?xml version='1.0' encoding='utf-8'?>" +
               "<mission " +
                "start_id='" + startMomentId + "' " +
                "name='" + missionName + "' >";
    }

    /**
     * A helper function to create an XML string representing the end of a mission.
     * @return A mission XML end-tag: </mission>
     */
    private String createEndMissionXml() {
        return "</mission>";
    }

    /**
     * A helper function to create an XML string representing the start of a generic moment.
     * @param momentType The type of moment to be created.
     * @param momentId The unique id of this moment.
     * @return An XML string of the following format:
     *      <moment type="[momentType]" id="[momentId]">
     */
    private String createStartMomentXml(String momentType, String momentId) {
        return  "<moment " +
                "type='" + momentType + "' " +
                "id='" + momentId + "' >";
    }

    /**
     * A helper function to create an XML string representing the end of a moment.
     * @return A moment XML end-tag: </moment>
     */
    private String createEndMomentXml() {
        return "</moment>";
    }

    /**
     * A helper function to create an XML string representing a timer moment.
     * @param momentId The unique id of the moment.
     * @param nextMomentId The id of the next moment if this choice is chosen.  Can be null if this
     *                     moment is the last moment in the mission.
     * @param lengthMinutes The length of the timer moment in minutes.
     * @return An XML string of the following format:
     *      <moment type="timer" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <length_minutes>[lengthMinutes]</length_minutes>
     *      </moment>
     */
    private String createTimerMomentXml(String momentId, String nextMomentId,
                                        double lengthMinutes) {
        String xml = "";

        xml += createStartMomentXml("timer", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += createLengthMinutesXml(lengthMinutes);
        xml += createEndMomentXml();

        return xml;
    }

    /**
     * A helper function to create an XML string representing a 'next_moment id' attribute.
     * All moment types can have 'next_moment id''s, and the lack of a 'next_moment id' signifies
     * that the current moment is the last moment in the mission.
     * @param nextMomentId The id of the next moment if this choice is chosen.  Can be null if this
     *                     moment is the last moment in the mission.
     * @return If 'nextMomentId' is null, return an empty string.  Otherwise, an XML string of the
     * following format is returned:
     *      <next_moment id="[nextMomentId]" />
     */
    private String createNextMomentXml(String nextMomentId) {
        if (nextMomentId == null) {
            return "";
        }
        return "<next_moment id='" + nextMomentId + "' />";
    }

    /**
     * A helper function to create an XML string representing the start of a choice moment.
     * @param momentId The unique id of the moment.
     * @param timeoutLengthMinutes The length of time in minutes after which the default choice
     *                             should be chosen for the user.
     * @param choiceDescription A meaningful, human-readable description of the choice to be made,
     *                          which is most likely in the form of either a direct or indirect
     *                          question.
     * @param defaultChoiceId The id of the choice that should be chosen if the decision times out
     *                        before the user has chosen their own choice.
     * @return An XML string with the following format:
     *      <moment type="choice" id="[momentId]">
     *          <timeout_length_minutes>[timeoutLengthMinutes]</timeout_length_minutes>
     *          <description>[choiceDescription]</description>
     *          <default_choice id="[defaultChoiceId]"/>
     *      </moment>
     */
    private String createStartChoiceMomentXml(String momentId, double timeoutLengthMinutes,
                                              String choiceDescription, String defaultChoiceId) {
        String xml = "";

        xml += createStartMomentXml("choice", momentId);
        xml += "<timeout_length_minutes>" + Double.toString(timeoutLengthMinutes)
                + "</timeout_length_minutes>";
        xml += "<description>" + choiceDescription + "</description>";
        xml += "<default_choice id='" + defaultChoiceId + "'/>";

        return xml;
    }

    /**
     * A helper function to create an XML string representing a choice.  Choices are found within
     * choice moments.
     * @param choiceId The id of the choice.
     * @param choiceDescription A meaningful, human-readable description of the choice.
     * @param nextMomentId The id of the next moment if this choice is chosen.  Can be null if this
     *                     moment is the last moment in the mission.
     * @param depleteWeaponCharge Whether the user's weapon should be depleted if they select this
     *                            choice.
     * @param incrementNumEnemiesDefeated Whether the number of enemies defeated should be
     *                                    incremented if the user selects this choice.
     * @param iconResourceName The name of the drawable resource for this choice action.
     * @return An XML string with the following format:
     *      <choice id="[choiceId]">
     *          <description>[choiceDescription]</description>
     *          <next_moment id="[nextMomentId]"/>
     *          <outcome
     *              deplete_weapon="[depleteWeaponCharge]"
     *              increment_enemies="[incrementNumEnemiesDefeated]"/>
     *          <icon name="[iconResourceName]" />
     *      </choice>
     */
    private String createChoiceXml(String choiceId, String choiceDescription, String nextMomentId,
                                   boolean depleteWeaponCharge,
                                   boolean incrementNumEnemiesDefeated, String iconResourceName) {
        String xml = "";

        xml += "<choice ";
        xml += "id='" + choiceId + "' >";
        xml += "<description>" + choiceDescription + "</description>";
        xml += createNextMomentXml(nextMomentId);
        xml += "<outcome ";
        xml += "deplete_weapon='" + Boolean.toString(depleteWeaponCharge) + "' ";
        xml += "increment_enemies='" + Boolean.toString(incrementNumEnemiesDefeated) + "' />";
        if (iconResourceName != null) {
            xml += createIconXML(iconResourceName);
        }
        xml += "</choice>";

        return xml;
    }

    /**
     * A helper function to create an XML string representing the end of a choice moment.
     * @return A moment XML end-tag: </moment>
     */
    private String createEndChoiceMomentXml() {
        return createEndMomentXml();
    }

    /**
     * A helper function to create an XML string representing a spoken text moment.
     * @param momentId The unique id of this moment.
     * @param nextMomentId The id of the next moment in the mission.  Can be null if this moment is
     *                     the last moment in the mission.
     * @param textToSpeak A string that should be spoken aloud as part of this moment.
     * @return An XML string with the following format:
     *      <moment type="spoken_text" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <text_to_speak>[textToSpeak]</text_to_speak>
     *      </moment>
     */
    private String createSpokenTextMomentXml(String momentId, String nextMomentId,
                                             String textToSpeak) {
        String xml = "";

        xml += createStartMomentXml("spoken_text", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += "<text_to_speak>" + textToSpeak + "</text_to_speak>";
        xml += createEndMomentXml();

        return xml;
    }

    /**
     * A helper function to create an XML string representing a sfx moment.
     * @param momentId The unique id of this moment.
     * @param nextMomentId The id of the next moment in the mission. Can be null if this moment is
     *                     the last moment in the mission.
     * @param pathToResource The absolute path to the sound resource to be played as part of this
     *                       moment.
     * @return An XML string with the following format:
     *      <moment type="sfx" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <uri>[pathToResource]</uri>
     *       </moment>
     */
    private String createSfxMomentXml(String momentId, String nextMomentId, String pathToResource) {
        String xml = "";

        xml += createStartMomentXml("sfx", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += "<uri>" + pathToResource + "</uri>";
        xml += createEndMomentXml();

        return xml;
    }

    /**
     * A helper function to create an XML string representing a timer moment's 'length_minutes'
     * element.
     * @param lengthMinutes The length of the timer moment in minutes.
     * @return An XML string with the following format:
     *      <length_minutes>[lengthMinutes]</length_minutes>
     */
    private String createLengthMinutesXml(double lengthMinutes) {
        return "<length_minutes>" + Double.toString(lengthMinutes) + "</length_minutes>";
    }

    /**
     * A helper function to create an XML string representing a 'fictional_progress' element.
     * @param progressDescription The fictional progress made.
     * @return An XML string with the following format:
     *      <fictional_progress>[progressDescription]</fictional_progress>
     */
    private String createFictionalProgressXml(String progressDescription) {
        return "<fictional_progress>" + progressDescription + "</fictional_progress>";
    }

    /**
     * A helper function to create an XML string representing an 'icon' element.
     * @param iconResourceName The name of the drawable resource for this choice action.
     * @return An XML string with the following format:
     *      <icon name="[iconResourceName]" />
     */
    private String createIconXML(String iconResourceName) {
        return "<icon name='" + iconResourceName + "'/>";
    }

    /**
     * A helper function to create an XML string representing a spoken text moment with fictional
     * progress.
     * @param momentId The unique id of this moment.
     * @param nextMomentId The id of the next moment in the mission.  Can be null if this moment is
     *                     the last moment in the mission.
     * @param textToSpeak A string that should be spoken aloud as part of this moment.
     * @param progressDescription A string describing the fictional progress.
     * @return An XML string with the following format:
     *      <moment type="spoken_text" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <text_to_speak>[textToSpeak]</text_to_speak>
     *          <fictional_progress>[progressDescription]</fictional_progress>
     *      </moment>
     */
    private String createSpokenTextMomentWithFictionalProgressXML(String momentId,
            String nextMomentId, String textToSpeak, String progressDescription) {
        String xml = "";
        xml += createStartMomentXml("spoken_text", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += "<text_to_speak>" + textToSpeak + "</text_to_speak>";
        xml += createFictionalProgressXml(progressDescription);
        xml += createEndMomentXml();
        return xml;
    }

    /**
     * A helper function to create an XML string representing a timer moment with fictional
     * progress.
     * @param momentId The unique id of the moment.
     * @param nextMomentId The id of the next moment if this choice is chosen.  Can be null if this
     *                     moment is the last moment in the mission.
     * @param lengthMinutes The length of the timer moment in minutes.
     * @param progressDescription A string describing the fictional progress.
     * @return An XML string of the following format:
     *      <moment type="timer" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <length_minutes>[lengthMinutes]</length_minutes>
     *          <fictional_progress>[progressDescription]</fictional_progress>
     *      </moment>
     */
    private String createTimerMomentWithFictionalProgressXML(String momentId, String nextMomentId,
            double lengthMinutes, String progressDescription) {
        String xml = "";

        xml += createStartMomentXml("timer", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += createLengthMinutesXml(lengthMinutes);
        xml += createFictionalProgressXml(progressDescription);
        xml += createEndMomentXml();

        return xml;
    }

    /**
     * A helper function to create an XML string representing a sfx moment with fictional progress.
     * @param momentId The unique id of this moment.
     * @param nextMomentId The id of the next moment in the mission. Can be null if this moment is
     *                     the last moment in the mission.
     * @param pathToResource The absolute path to the sound resource to be played as part of this
     *                       moment.
     * @param progressDescription A string describing the fictional progress.
     * @return An XML string with the following format:
     *      <moment type="sfx" id="[momentId]">
     *          <next_moment id="[nextMomentId]" />
     *          <uri>[pathToResource]</uri>
     *          <fictional_progress>[progressDescription]</fictional_progress>
     *       </moment>
     */
    private String createSfxMomentWithFictionalProgressXML(String momentId, String nextMomentId,
            String pathToResource, String progressDescription) {
        String xml = "";

        xml += createStartMomentXml("sfx", momentId);
        xml += createNextMomentXml(nextMomentId);
        xml += "<uri>" + pathToResource + "</uri>";
        xml += createFictionalProgressXml(progressDescription);
        xml += createEndMomentXml();

        return xml;

    }

    /**
     * A helper function to create an XML string representing the start of a choice moment with
     * fictional progress.
     * @param momentId The unique id of the moment.
     * @param timeoutLengthMinutes The length of time in minutes after which the default choice
     *                             should be chosen for the user.
     * @param choiceDescription A meaningful, human-readable description of the choice to be made,
     *                          which is most likely in the form of either a direct or indirect
     *                          question.
     * @param defaultChoiceId The id of the choice that should be chosen if the decision times out
     *                        before the user has chosen their own choice.
     * @param progressDescription A string describing the fictional progress.
     * @return An XML string with the following format:
     *      <moment type="choice" id="[momentId]">
     *          <timeout_length_minutes>[timeoutLengthMinutes]</timeout_length_minutes>
     *          <description>[choiceDescription]</description>
     *          <default_choice id="[defaultChoiceId]"/>
     *          <fictional_progress>[progressDescription]</fictional_progress>
     */
    private String createStartChoiceMomentWithFictionalProgressXML (String momentId,
            double timeoutLengthMinutes, String choiceDescription, String defaultChoiceId,
            String progressDescription) {
        String xml = "";
        xml += createStartChoiceMomentXml(momentId, timeoutLengthMinutes, choiceDescription,
                defaultChoiceId);
        xml += createFictionalProgressXml(progressDescription);
        return xml;
    }

    /**
     * A helper function to create an XML string representing a choice.  Choices are found within
     * choice moments. Contains a piece of fictional progress.
     * @param choiceId The id of the choice.
     * @param choiceDescription A meaningful, human-readable description of the choice.
     * @param nextMomentId The id of the next moment if this choice is chosen.  Can be null if this
     *                     moment is the last moment in the mission.
     * @param depleteWeaponCharge Whether the user's weapon should be depleted if they select this
     *                            choice.
     * @param incrementNumEnemiesDefeated Whether the number of enemies defeated should be
     *                                    incremented if the user selects this choice.
     * @param iconResourceName The name of the drawable resource for this choice action.
     * @return An XML string with the following format:
     *      <choice id="[choiceId]">
     *          <description>[choiceDescription]</description>
     *          <next_moment id="[nextMomentId]"/>
     *          <outcome
     *              deplete_weapon="[depleteWeaponCharge]"
     *              increment_enemies="[incrementNumEnemiesDefeated]"/>
     *          <fictional_progress>[progressDescription]</fictional_progress>
     *          <icon name="[iconResourceName]" />
     *      </choice>
     */
    private String createChoiceWithFictionalProgressXML(String choiceId, String choiceDescription,
            String nextMomentId, boolean depleteWeaponCharge, boolean incrementNumEnemiesDefeated,
            String progressDescription, String iconResourceName) {
        String xml = "";

        xml += "<choice ";
        xml += "id='" + choiceId + "' >";
        xml += "<description>" + choiceDescription + "</description>";
        xml += createNextMomentXml(nextMomentId);
        xml += "<outcome ";
        xml += "deplete_weapon='" + Boolean.toString(depleteWeaponCharge) + "' ";
        xml += "increment_enemies='" + Boolean.toString(incrementNumEnemiesDefeated) + "' />";
        xml += createFictionalProgressXml(progressDescription);
        if (iconResourceName != null) {
            xml += createIconXML(iconResourceName);
        }
        xml += "</choice>";

        return xml;
    }
}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.fpl.gim.examplegame" >
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
    <uses-feature android:name="android.hardware.location" android:required="true"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.google.fpl.gim.examplegame.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".MainService">
        </service>

        <meta-data android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <meta-data android:name="com.google.android.gms.games.APP_ID"
            android:value="@string/app_id" />

    </application>
</manifest>


================================================
FILE: app/src/main/assets/legacy_missions/choice_mission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of a choice moment and a timer moment. -->
<mission
    start_id="start"
    name="Choice mission - test">
    <moment
        type="choice"
        id="start">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>What will you do?</description>
        <default_choice id="choice_smoke_bomb" />
        <choice
            id="fire">
            <description>Fire!!!</description>
            <next_moment id="timer_1"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_sling_shot">
            <description>Use a sling shot.</description>
            <next_moment id="timer_1"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_sling_shot_white"/>
        </choice>
        <choice
            id="choice_smoke_bomb">
            <description>Drop a smoke bomb.</description>
            <next_moment id="timer_1"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_smoke_bomb_white"/>
        </choice>
    </moment>
    <moment
        type="timer"
        id="timer_1">
        <length_minutes>0.1</length_minutes>
    </moment>
</mission>


================================================
FILE: app/src/main/assets/legacy_missions/mission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- An example mission. -->
<!-- Mission
    start_id: The id signifying the starting Moment for this mission. -->
<mission
    start_id="start"
    name="4 Moment Example - runs only one timer moment">
    <!-- All Moments have:
        type: The kind of Moment.
        id: A String identifier unique to each Moment. -->
    <!-- All Moments may optionally have:
        fictional_progress: A string detailing a fictional achievement for the player.
                            There may be multiple pieces of fictional progress. -->
    <!-- TimerMoment -->
    <moment
        type="timer"
        id="start">
        <!-- TimerMoments have:
            next_moment: The next Moment to progress to once timer is complete.
            length_minutes: Time for this TimerMoment to run. -->
        <length_minutes>0.25</length_minutes>
        <fictional_progress>Created a mission.</fictional_progress>
        <fictional_progress>Made a timer moment</fictional_progress>
    </moment>
    <!-- ChoiceMoment -->
    <moment
        type="choice"
        id="choice_example" >
        <!-- ChoiceMoments have:
            timeout_length_minutes: The time to wait until the default choice is executed.
            description: A text description of the choice to be made, displayed to user.
            default_choice: The Choice to execute if the Choice times out.
            choice(s): At least one Choice to be presented to the user. -->
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>Example ChoiceMoment Description</description>
        <default_choice id="choice_2" />
        <!-- Choices have:
            id: A String identifier unique to each choice. -->
        <!-- There must be at least 2 Choices in each ChoiceMoment.  One of these 2 Choices must
            have an id of "fire" to indicate that this Choice will appear only if the user's weapon
            is charged. There can a maximum of 3 choices per ChoiceMoment because notifications can
            have no more than 3 actions. -->
        <choice
            id="fire">
            <!-- Choices contain:
                next_moment: The Moment to progress to if this Choice is selected.
                outcome: The Outcome containing consequences of this Choice.
                description: Textual description of this option. -->
            <!-- Choices may optionally contain:
                fictional_progress: A string detailing a fictional achievement for the player.
                            There may be multiple pieces of fictional progress. -->
            <description>Example Choice Description 1</description>
            <next_moment id="start"/>
            <!-- Outcomes have:
                deplete_weapon: A boolean whether the weapon is fired as a result of this option.
                increment_enemies: A boolean whether an enemy is defeated as a result of this
                    option. -->
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <fictional_progress>Fired a weapon.</fictional_progress>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_2">
            <description>Example Choice Description 2</description>
            <next_moment id="start"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_3">
            <description>Example Choice Description 2</description>
            <next_moment id="start"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
    </moment>
    <!-- SfxMoment -->
    <moment
        type="sfx"
        id="sfx1">
        <!-- SfxMoments have:
            next_moment: The next Moment to progress to once timer is complete.
            uri: The URI path to a sound effect to play. -->
        <next_moment id="start"/>
        <uri>path/to/something</uri>
    </moment>
    <!-- SpokenTextMoment -->
    <moment
        type="spoken_text"
        id="text1">
        <!-- SpokenTextMoments have:
            next_moment: The next Moment to progress to once timer is complete.
            text_to_speak: The text to be spoken. -->
        <next_moment id="start"/>
        <text_to_speak>Hello!</text_to_speak>
    </moment>
</mission>


================================================
FILE: app/src/main/assets/legacy_missions/sfx mission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of one sfx and two spoken text moments. -->
<mission
    start_id="new"
    name="SFX Example">
    <moment
        type="spoken_text"
        id="new">
        <next_moment id="start"/>
        <text_to_speak>You will now hear a snap sound.</text_to_speak>
    </moment>
    <moment
        type="sfx"
        id="start">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/snap</uri>
        <next_moment id="end"/>
    </moment>
    <moment
        type="spoken_text"
        id="end">
        <text_to_speak>That was a snap! You're welcome.</text_to_speak>
        </moment>
</mission>

================================================
FILE: app/src/main/assets/legacy_missions/spoken_plus_timer_mission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of spoken text and timer moments. -->
<mission
    start_id="start"
    name="Spoken + Timer Example">
    <moment
        type="spoken_text"
        id="start">
        <next_moment id="second"/>
        <text_to_speak>
            This is the first moment in this mission. The next moment should be a timer moment
            lasting 6 seconds.
        </text_to_speak>
    </moment>
    <moment
        type="timer"
        id="second">
        <next_moment id="third"/>
        <length_minutes>0.1</length_minutes>
    </moment>
    <moment
        type="spoken_text"
        id="third">
        <text_to_speak>
            This is the final moment, and the previous moment lasted 6 seconds.
        </text_to_speak>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/legacy_missions/texttospeechmission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting only of spoken text moments. -->
<mission
    start_id="start"
    name="TTS Example">
    <moment
        type="spoken_text"
        id="start">
        <next_moment id="second"/>
        <text_to_speak>Hello!</text_to_speak>
    </moment>
    <moment
        type="spoken_text"
        id="second">
        <text_to_speak>Agent, your mission is simple. Good luck. Godspeed!</text_to_speak>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/legacy_missions/timermission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting only of timer moments. -->
<mission
    start_id="start"
    name="Timer Moments only Example">
    <moment
        type="timer"
        id="start">
        <next_moment id="second"/>
        <length_minutes>0.1</length_minutes>
    </moment>
    <moment
        type="timer"
        id="second">
        <next_moment id="third"/>
        <length_minutes>0.1</length_minutes>
    </moment>
    <moment
        type="timer"
        id="third">
        <length_minutes>0.1</length_minutes>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/01_sample_mission_1.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of a complete interaction. First, the player is welcomed to the
game and given context and a challenge (spoken text moment.)  Then, the player runs for 1.5
minutes (timer moment.)  Next, the player is notified that a zombie is now chasing them (spoken text
moment) and must choose one of two ways to defeat the zombie (choice moment.)  Finally, the user
learns the effectiveness of their choice (zombie defeated or not) (sfx moment and spoken text
moment) and is notified that they have arrived at the base (spoken text moment.) -->
<mission
    start_id="start"
    name="Complete Mission #1">
    <moment
        type="spoken_text"
        id="start">
        <next_moment id="timer_1"/>
        <text_to_speak>
            Welcome to Games In Motion!  The year is 2012, and the apocalypse is upon us in the form
            of zombies.  The zombies have decided that your brain looks particularly tasty, and a
            swarm is after you.  You will be pursued relentlessly until you reach the base, which is
            approximately 3 minutes away by foot.  You will be notified when a zombie begins to
            chase you, and you will have to react appropriately.  Keep an eye on your wrist gadget,
            but don't run into any trees, or else the zombies will surely catch you.  Good luck!
            Start running now!  You have at least a minute before a zombie will begin to chase you.
        </text_to_speak>
        <fictional_progress>Started a mission</fictional_progress>
        <fictional_progress>Received instructions</fictional_progress>
    </moment>

    <moment
        type="timer"
        id="timer_1">
        <next_moment id="zombie_chase_1"/>
        <length_minutes>3</length_minutes>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_chase_1">
        <next_moment id="zombie_choice_1"/>
        <text_to_speak>
            You are now being chased!  Your options are as follows: Number 1. Act like a zombie in
            order to blend in.  Number 2.  Throw an axe at the zombie.  Make your choice using your
            mobile device.  Choose wisely.
        </text_to_speak>
        <fictional_progress>Encountered a zombie</fictional_progress>
    </moment>

    <moment
        type="choice"
        id="zombie_choice_1">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>What will you do?</description>
        <default_choice id="choice_blend" />
        <choice
            id="fire">
            <description>Fire!!!</description>
            <next_moment id="zombie_axe_success"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_axe">
            <description>Axe.</description>
            <next_moment id="sfx_zombie_axe"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="true"/>
            <fictional_progress>Threw an axe.</fictional_progress>
            <icon name="ic_axe_white"/>
        </choice>
        <choice
            id="choice_blend">
            <description>Blend.</description>
            <next_moment id="sfx_zombie_blend"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <fictional_progress>Pretended to be a zombie</fictional_progress>
            <icon name="ic_blend_zombie_white"/>
        </choice>
    </moment>

    <moment
        type="sfx"
        id="sfx_zombie_axe">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/axe</uri>
        <next_moment id="zombie_axe_success"/>
    </moment>

    <moment
        type="sfx"
        id="sfx_zombie_blend">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/brains</uri>
        <next_moment id="zombie_blend_failure"/>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_axe_success">
        <next_moment id="final_fiction"/>
        <text_to_speak>
            You defeated the zombie!  Good thing because
        </text_to_speak>
        <fictional_progress>Killed the zombie!</fictional_progress>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_blend_failure">
        <next_moment id="final_fiction"/>
        <text_to_speak>
            You were caught!  That's okay because
        </text_to_speak>
        <fictional_progress>Failed at pretending to be a zombie.</fictional_progress>
    </moment>

    <moment
        type="spoken_text"
        id="final_fiction">
        <!--<next_moment id="timer_1"/>-->
        <text_to_speak>
            You have arrived at the base.
        </text_to_speak>
        <fictional_progress>Arrived at the base.</fictional_progress>
    </moment>
</mission>


================================================
FILE: app/src/main/assets/missions/02_sample_mission_2.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of 4 complete interactions. -->

<mission
    start_id="start"
    name="Complete Mission #2">

    <!-- Introductory moment explaining the scenario and basic game. -->
    <moment
        type="spoken_text"
        id="start">
        <next_moment id="timer_1"/>
        <text_to_speak>
            Hello Agent. Welcome to Games In Motion!  The year is 2012, and the apocalypse is upon
            us in the form of zombies. Your task is to make it to our base. But the zombies have
            decided that your brain looks particularly tasty, and a swarm is after you.  You will be
            pursued relentlessly until you reach the base, which is approximately 20 minutes away by
            foot. Keep an eye on your wrist gadget: you will be notified when a zombie begins to
            chase you, and you will have to react appropriately. Don’t run into any trees, or else
            the zombies will surely catch you. We’ve provided you with a simple super charger. Run
            faster, and you’ll get a shot to use against the zombies. Good luck! Start running now!
            You have at least a minute before a zombie will begin to chase you.
        </text_to_speak>
        <fictional_progress>Agent: Apocalypse</fictional_progress>
    </moment>

    <!-- Basic timer moment to space out events. -->
    <moment
        type="timer"
        id="timer_1">
        <next_moment id="zombie_chase_1"/>
        <length_minutes>3.0</length_minutes>
    </moment>

    <!-- First choice/first zombie encounter. -->
    <moment
        type="spoken_text"
        id="zombie_chase_1">
        <next_moment id="zombie_choice_1"/>
        <text_to_speak>
            Watch out, there is a zombie behind you! Look at your wrist gadget and act quickly to
            defeat the them. If you’ve got a shot, you can take it. Otherwise, you can act like a
            zombie in order to blend in.  Or, throw an axe at the zombie. Choose wisely.
        </text_to_speak>
    </moment>

    <moment
        type="choice"
        id="zombie_choice_1">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>Face the first zombie!</description>
        <default_choice id="choice_blend" />
        <choice
            id="fire">
            <description>Fire!!!</description>
            <next_moment id="sfx_zombie_fire_1"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_axe">
            <description>Axe.</description>
            <next_moment id="sfx_zombie_axe_1"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="true"/>
            <icon name="ic_axe_white"/>
        </choice>
        <choice
            id="choice_blend">
            <description>Blend.</description>
            <next_moment id="sfx_zombie_blend_1"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_blend_zombie_white"/>
        </choice>
    </moment>

    <!--Sound effects from choice 1-->
    <moment
        type="sfx"
        id="sfx_zombie_fire_1">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/fire</uri>
        <!--TODO: make a fire sound effect-->
        <next_moment id="zombie_fire_1_success"/>
    </moment>

    <moment
        type="sfx"
        id="sfx_zombie_axe_1">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/axe</uri>
        <next_moment id="zombie_axe_1_success"/>
    </moment>

    <moment
        type="sfx"
        id="sfx_zombie_blend_1">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/brains</uri>
        <next_moment id="zombie_blend_1_failure"/>
    </moment>

    <!-- Fiction from Choice 1 -->

    <moment
        type="spoken_text"
        id="zombie_fire_1_success">
        <text_to_speak>
            Right in the eye! Nice work, agent. You just might have this zombie thing down.
        </text_to_speak>
        <next_moment id="timer_2_success"/>
    </moment>
    <moment
        type="spoken_text"
        id="zombie_axe_1_success">
        <text_to_speak>
            Hum. I did not expect that to work. Well, keep it up!
        </text_to_speak>
        <next_moment id="timer_2_success"/>
    </moment>
    <moment
        type="spoken_text"
        id="zombie_blend_1_failure">
        <text_to_speak>
            Why on earth did you think blending in would work? You barely got out of that! You’ll never be a great agent if you act like that.
        </text_to_speak>
        <next_moment id="timer_2_failure"/>
    </moment>

    <!-- Timer moment spacing out next event. -->

    <moment
        type="timer"
        id="timer_2_success">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_2a"/>
    </moment>

    <moment
        type="timer"
        id="timer_2_failure">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_2b"/>
    </moment>

    <!-- Fiction readying next choice. -->

    <moment
        type="spoken_text"
        id="prompt_2a">
        <text_to_speak>
            All right agent, I have a real task for you now.
        </text_to_speak>
        <next_moment id="prompt_2"/>
    </moment>

    <moment
        type="spoken_text"
        id="prompt_2b">
        <text_to_speak>
            How would you like a chance to redeem yourself?
        </text_to_speak>
        <next_moment id="prompt_2"/>
    </moment>

    <!-- Reconciliation - just one choice, regardless of first choice. -->

    <moment
        type="spoken_text"
        id="prompt_2">
        <text_to_speak>
            There’s a fork in the road ahead. We have intel suggesting that a zombie mans the split.
            One of the paths leads to a bunker, and that bunker has a hard drive we need you to get.
            Don’t worry if you can’t find it right away - but know that the zombie may pursue if you
            get their attention. If you take care of them quickly, they might have something on
            their person. Or is it just a body since they are dead? Undead? Anyway, it will tell you
            where to go.
        </text_to_speak>
        <next_moment id="choice_2"/>
    </moment>

    <!--Fork in the road choice.-->

    <moment
        type="choice"
        id="choice_2">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>Fork in the road</description>
        <default_choice id="choice_left_sneak" />
        <choice
            id="fire">
            <description>Fire!!!</description>
            <next_moment id="sfx_zombie_fire_2"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_left_sneak">
            <description>Left - sneak</description>
            <next_moment id="sfx_sneak"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="true"/>
            <icon name="ic_sneak_white"/>
        </choice>
        <choice
            id="choice_right_sneak">
            <description>Right - sneak</description>
            <next_moment id="sfx_sneak"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_sneak_white"/>
        </choice>
    </moment>

    <!--Sound effects for fork in the road.-->

    <moment
        type="sfx"
        id="sfx_zombie_fire_2">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/fire</uri>
        <next_moment id="zombie_fire_2_success"/>
    </moment>

    <moment
        type="sfx"
        id="sfx_sneak">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/sneak</uri>
        <!--TODO: make a sneak failure sfx-->
        <next_moment id="zombie_sneak_failure"/>
    </moment>

    <!--Fictional following fork in the road choice-->

    <moment
        type="spoken_text"
        id="zombie_fire_2_success">
        <text_to_speak>
            Well done. And I was right, of course. The zombie had a slip of paper saying to go left.
            Head down the path. It will be obvious when you get there.
        </text_to_speak>
        <next_moment id="timer_3_success"/>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_sneak_failure">
        <text_to_speak>
            You certainly have his attention now. How disappointing. And, and, what are you doing?
            Do you really think jumping into the bramble will help you here? You're just making
            noise. Perhaps if you're extraordinarily lucky, you can lose the zombie on your tail.
            Sigh. Now I have to make contingency plans to recover that hard drive, all because of
            your stupidity.
        </text_to_speak>
        <next_moment id="timer_3_failure"/>
    </moment>

    <!--Timer moments spacing out the next moment.-->

    <moment
        type="timer"
        id="timer_3_success">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_3a"/>
    </moment>

    <moment
        type="timer"
        id="timer_3_failure">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_3b"/>
    </moment>


    <!-- Arrival to bunker -->

    <moment
        type="spoken_text"
        id="prompt_3a">
        <text_to_speak>
            Right time, right location. I like your consistency, agent. Much better than the other
            candidates we were investigating. You are doing so well that I apologize for the
            pedantic explanation of your next task.
        </text_to_speak>
        <next_moment id="prompt_3"/>
    </moment>

    <moment
        type="spoken_text"
        id="prompt_3b">
        <text_to_speak>
            I am in shock at your luck. How did you lose that zombie? I swear they were following
            you the entire time. And how did you find the bunker after all that? You do realize
            that good luck does not a good agent make? But as long as you are here, you might as
            well be useful.
        </text_to_speak>
        <next_moment id="prompt_3"/>
    </moment>

    <moment
        type="spoken_text"
        id="prompt_3">
        <text_to_speak>
            Go inside, go downstairs, turn left. 3 doors down will be a room labeled Animatrix.
            You'll need to break the window, get in, and grab the blue external hard drive next to
            the large computer. You will lose contact in there, but I will check in again once you
            get out.
        </text_to_speak>
        <next_moment id="timer_4"/>
    </moment>

    <!-- Timer to space out. -->

    <moment
        type="timer"
        id="timer_4">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_4"/>
    </moment>

    <!-- Get out of bunker. Horde! -->

    <moment
        type="spoken_text"
        id="prompt_4">
        <text_to_speak>
            You got out.  By the way, there is a horde of zombies chasing you.  Why aren’t you
            running now?  Didn’t I warn you about them?  We saw them on the radar nearly 10 minutes
            ago.  Oh well.  Better late than never I suppose.  Start running now!
        </text_to_speak>
        <next_moment id="timer_5"/>
    </moment>

    <moment
        type="timer"
        id="timer_5">
        <length_minutes>3.0</length_minutes>
        <next_moment id="prompt_5"/>
    </moment>

    <moment
        type="spoken_text"
        id="prompt_5">
        <text_to_speak>
            You are at the base now, but the horde is still chasing you.  The horde is thinner now,
            but we can’t let you in until you defeat them.  Our own safety is much more important
            than yours, after all.  Deal with them. Or we will deal with you.
        </text_to_speak>
        <next_moment id="final_choice"/>
    </moment>

    <moment
        type="choice"
        id="final_choice">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>The horde is here.</description>
        <default_choice id="join_them" />
        <choice
            id="fire">
            <description>Fire!!!</description>
            <next_moment id="sfx_zombie_fire_final"/>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="join_them">
            <description>Join them.</description>
            <next_moment id="sfx_join_them"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <icon name="ic_blend_zombie_white"/>
        </choice>
        <choice
            id="jetpack">
            <description>Jetpack!</description>
            <next_moment id="sfx_jetpack"/>
            <outcome
                deplete_weapon="false"
                increment_enemies="true"/>
            <icon name="ic_jet_pack_white"/>
        </choice>
    </moment>

    <moment
        type="sfx"
        id="sfx_zombie_fire_final">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/fire</uri>
        <next_moment id="zombie_fire_final_success"/>
    </moment>

    <moment
        type="sfx"
        id="stx_join_them">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/brains</uri>
        <next_moment id="zombie_join_them_failure"/>
    </moment>

    <moment
        type="sfx"
        id="sfx_jetpack">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/jetpack</uri>
        <next_moment id="zombie_jetpack_success"/>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_fire_final_success">
        <text_to_speak>
            I suppose that firing like a maniac into a horde of zombies is one way to go.
            Shockingly, you were successful.  I guess that means we have to let you in now.
            Darn it.  Well, you completed the mission, so you are now an agent.  I begrudgingly
            offer my congratulations.
        </text_to_speak>
        <next_moment id="mission_end"/>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_join_them_failure">
        <text_to_speak>
            You have decided to join them?  How rude.  Needless to say, you did not complete the
            mission, and your agent status has been revoked.  We might consider granting it to you
            again, but not until you return for your next mission.  Farewell.
        </text_to_speak>
        <next_moment id="mission_end"/>
    </moment>

    <moment
        type="spoken_text"
        id="zombie_jetpack_success">
        <text_to_speak>
            Where did that jetpack come from? And how inconsiderate of you to leave us behind to
            deal with the horde.  Needless to say, you did not complete the mission, and your agent
            status has been revoked.  We might consider granting it to you again, but not until you
            return for your next mission.  Farewell.
        </text_to_speak>
        <next_moment id="mission_end"/>
    </moment>

    <moment
        type="spoken_text"
        id="mission_end">
        <text_to_speak>Mission end.</text_to_speak>
    </moment>

</mission>








================================================
FILE: app/src/main/assets/missions/ex_01_timer_moment.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A 1 minute timer moment for 8/11 demo and presentation. -->
<mission
    start_id="start"
    name="Timer Example">
    <moment
        type="timer"
        id="start">
        <length_minutes>0.5</length_minutes>
        <fictional_progress>Played a mission!</fictional_progress>
        <fictional_progress>Played a timer moment</fictional_progress>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/ex_02_spoken_text_moment.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- Spoken text moment for 8/11 demo and presentation. -->
<mission
    start_id="start"
    name="Spoken Text Example">
    <moment
        type="spoken_text"
        id="start">
        <text_to_speak>
            Hello! This is a spoken text moment that uses text to speech.
        </text_to_speak>
        <fictional_progress>Heard a spoken text moment</fictional_progress>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/ex_03_choice_moment.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- Choice moment for 8/11 demo and presentation.. -->
<mission
    start_id="start"
    name="Choice Example">
    <moment
        type="choice"
        id="start">
        <timeout_length_minutes>0.5</timeout_length_minutes>
        <description>What will you do?</description>
        <default_choice id="choice_smoke_bomb" />
        <fictional_progress>Made a choice</fictional_progress>
        <choice
            id="fire">
            <description>Fire!!!</description>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <icon name="ic_fire_white"/>
        </choice>
        <choice
            id="choice_sling_shot">
            <description>Use a sling shot.</description>
            <outcome
                deplete_weapon="false"
                increment_enemies="false"/>
            <fictional_progress>Used a slingshot</fictional_progress>
            <icon name="ic_sling_shot_white"/>
        </choice>
        <choice
            id="choice_smoke_bomb">
            <description>Drop a smoke bomb.</description>
            <outcome
                deplete_weapon="true"
                increment_enemies="false"/>
            <fictional_progress>Used a smoke bomb</fictional_progress>
            <icon name="ic_smoke_bomb_white"/>
        </choice>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/ex_04_sfx_moment.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- Sfx moment for 8/11 demo and presentation. -->
<mission
    start_id="start"
    name="Sfx Example">
    <moment
        type="sfx"
        id="start">
        <uri>android.resource://com.google.fpl.gim.examplegame/raw/brains</uri>
        <fictional_progress>BRAINS</fictional_progress>
        <fictional_progress>BRAINS BRAINS BRAINS</fictional_progress>
        <fictional_progress>BRAAAAAAAAAAAAAAAINS</fictional_progress>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/ex_05_broken_timer_moment.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A 1 minute timer moment that does not have a 'length_minutes' element.  Demonstrates
XML parse error handling in the app. -->
<mission
    start_id="start"
    name="Timer Example - Broken">
    <moment
        type="timer"
        id="start">
        <fictional_progress>Played a mission!</fictional_progress>
        <fictional_progress>Played a timer moment</fictional_progress>
    </moment>
</mission>

================================================
FILE: app/src/main/assets/missions/ex_06_long_timer_mission.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2015 Google Inc. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!-- A mission example consisting of a single, very long timer moment. -->
<mission
    start_id="start"
    name="Long timer moment">
    <moment
        type="timer"
        id="start">
        <length_minutes>100.0</length_minutes>
    </moment>
</mission>

================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/Choice.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import java.util.ArrayList;

/**
 * Describes an option that a user has when presented with a decision to make.
 * Choices are only meaningful when there are 2+ of them.
 */
public class Choice {
    // Unique identifier for this Choice.
    private String mChoiceId;
    // The description of this option.
    private String mDescription;
    // The moment to go to next.
    private String mNextMomentId;
    // The set of changes to make if the player chooses this option.
    private Outcome mOutcome;
    // Whether or not this Choice requires a charged weapon.
    private boolean mRequiresChargedWeapon;
    // Fictional Progress associated with this choice.
    private ArrayList<String> mFictionalProgress;
    // The name of the resource icon to display for this Choice's action.
    private String mDrawableResourceName;

    public Choice(String choiceId, String text, String nextMomentId, Outcome outcome,
                  boolean requiresChargedWeapon, ArrayList<String> fictionalProgress,
                  String drawableResourceName) {
        mChoiceId = choiceId;
        mDescription = text;
        mNextMomentId = nextMomentId;
        mOutcome = outcome;
        mRequiresChargedWeapon = requiresChargedWeapon;
        mFictionalProgress = fictionalProgress;
        mDrawableResourceName = drawableResourceName;
    }

    public String getChoiceId() {
        return mChoiceId;
    }

    public String getDescription() {
        return mDescription;
    }

    public String getNextMomentId() {
        return mNextMomentId;
    }

    public Outcome getOutcome() {
        return mOutcome;
    }

    public ArrayList<String> getFictionalProgress() {
        return mFictionalProgress;
    }

    public boolean requiresChargedWeapon() {
        return mRequiresChargedWeapon;
    }

    public String getDrawableResourceName() {
        return mDrawableResourceName;
    }
}


================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMoment.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;

import com.google.fpl.gim.examplegame.gui.NotificationOptions;
import com.google.fpl.gim.examplegame.utils.Utils;

import java.util.ArrayList;

/**
 * Describes a Moment in which the user is presented with a decision to make.
 */
public class ChoiceMoment extends Moment {

    private static final String TAG = ChoiceMoment.class.getSimpleName();
    public static final int MAXIMUM_NUM_OF_CHOICES = 3;
    public static final int MINIMUM_NUM_OF_CHOICES = 2;

    private static final String CHOICE_NOTIFICATION_ACTION_1
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_1";
    private static final String CHOICE_NOTIFICATION_ACTION_2
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_2";
    private static final String CHOICE_NOTIFICATION_ACTION_3
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_3";
    private static final String CHOICE_ID_KEY
            = "com.google.fpl.gim.examplegame.CHOICE_ID_KEY";

    private static final String ICON_RESOURCE_FOLDER = "drawable";
    private static long[] VIBRATE_PATTERN = {0, 300, 100, 300, 100, 300};

    private ChoiceMomentData mData;
    private long mStartTimeNanos;
    private Choice mSelectedChoice = null;

    public ChoiceMoment (Mission mission, ChoiceMomentData data) {
        super(mission);
        this.mData = data;
    }

    public void update(long nowNanos) {
        Utils.logDebug(TAG, "ChoiceMoment \"" + mData.getMomentId() + "\" update.");
        if (!isDone() && hasTimeToMakeChoiceExpired(nowNanos)) {
            // Pick default choice
            if (noChoiceSelectedYet()) {
                selectChoice(mData.getDefaultChoiceId());
            }
            setIsDone(true);
        }
    }

    @Override
    public void start(long nowNanos) {
        super.start(nowNanos);
        Utils.logDebug(TAG, "ChoiceMoment \"" + mData.getMomentId() + "\" started.");
        setStartTimeNanos(nowNanos);

        // If the user's weapon is not charged, the choice to fire their weapon should not be
        // displayed.
        Choice[] choices = mData.getChoices();
        int numActions = mData.getNumChoices();
        if (!getMission().isWeaponCharged()) {
            numActions--;
        }

        // Create notification actions using the valid choices.
        NotificationCompat.Action[] actions = new NotificationCompat.Action[numActions];
        int index = 0;
        String[] allActions = {CHOICE_NOTIFICATION_ACTION_1, CHOICE_NOTIFICATION_ACTION_2,
                CHOICE_NOTIFICATION_ACTION_3};
        for (Choice choice : choices) {
            if (!choice.requiresChargedWeapon() || getMission().isWeaponCharged()) {
                // Bounds checked in MissionParser.java, which requires each choice moment to have
                // 2 or 3 choices associated with it.
                Intent actionIntent = new Intent(allActions[index]);
                actionIntent.putExtra(CHOICE_ID_KEY, choice.getChoiceId());

                String resourceName = choice.getDrawableResourceName();
                String packageName = getMission().getService().getPackageName();
                int resource = getMission().getService().getResources()
                        .getIdentifier(resourceName, ICON_RESOURCE_FOLDER, packageName);

                // If the resource does not exist, default to using application icon.
                if (resource == 0) {
                    resource = getMission().getService().getApplicationInfo().icon;
                }

                actions[index] = getMission().getService()
                        .makeNotificationAction(actionIntent, resource,
                        choice.getDescription());
                index++;
            }
        }

        // Create the notification to warn the user of an approaching enemy.
        NotificationOptions notificationOptions =
                NotificationOptions.getDefaultNotificationOptions();
        notificationOptions.setNotificationId(MainService.CHOICE_NOTIFICATION_ID);
        notificationOptions.setPriorityAsMax();
        notificationOptions.setActions(actions);
        notificationOptions.setNotificationDefaults(0);
        notificationOptions.setVibratePattern(VIBRATE_PATTERN);
        getMission().getService().postActionNotification(notificationOptions);
    }

    @Override
    public void end() {
        Utils.logDebug(TAG, "ChoiceMoment \"" + mData.getMomentId() + "\" ended.");
        // Remove choice notification.
        dismissNotification();
    }

    /**
     * The next moment is not defined for a ChoiceMoment until the user has selected a choice.
     * @return Returns null until a choice is made, then returns the nextMomentId.
     */
    @Override
    public String getNextMomentId() {
        if (mSelectedChoice == null) {
            return null;
        } else {
            return mSelectedChoice.getNextMomentId();
        }
    }

    @Override
    public void restart(long nowNanos) {
        start(nowNanos);
    }

    public ChoiceMomentData getMomentData() {
        return this.mData;
    }

    public void setStartTimeNanos(long startTimeNanos) {
        this.mStartTimeNanos = startTimeNanos;
    }

    public boolean hasTimeToMakeChoiceExpired(long nowNanos) {
        return (nowNanos - mStartTimeNanos) >= Utils.minutesToNanos(mData
                .getTimeoutLengthMinutes());
    }

    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(CHOICE_NOTIFICATION_ACTION_1)
                || intent.getAction().equals(CHOICE_NOTIFICATION_ACTION_2)
                || intent.getAction().equals(CHOICE_NOTIFICATION_ACTION_3)) {
            String choiceId = intent.getStringExtra(CHOICE_ID_KEY);
            selectChoice(choiceId);
        }
    }

    public synchronized void selectChoice(String choiceId) {
        if (!isDone()) {
            Utils.logDebug(TAG, "Choice with id \"" + choiceId + "\" selected.");
            mSelectedChoice = mData.getChoiceById(choiceId);
            getMission().applyOutcome(mSelectedChoice.getOutcome());
            setIsDone(true);
        }
    }

    public void dismissNotification() {
        NotificationManager notificationManager = (NotificationManager) getMission().getService()
                .getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(MainService.CHOICE_NOTIFICATION_ID);
    }

    public boolean noChoiceSelectedYet() {
        return mSelectedChoice == null;
    }

    @Override
    public ArrayList<String> getFictionalProgress() {
        ArrayList<String> progress = new ArrayList<>();
        progress.addAll(mData.getFictionalProgress());
        if (!noChoiceSelectedYet()) {
            progress.addAll(mSelectedChoice.getFictionalProgress());
        }
        return progress;
    }
}


================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMomentData.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * Encapsulates the data that is needed to define a unique ChoiceMoment.
 */
public class ChoiceMomentData extends MomentData {

    // The text describing the choice the player must make.
    private final String mDescription;
    // All of the choices that the user can have.
    private final HashMap<String, Choice> mChoices;
    // Identifies the default Choice to execute in the case of a timeout.
    private final String mDefaultChoiceId;
    // Time in minutes until the default Choice is executed.
    private final float mTimeoutLengthMinutes;

    /**
     * Constructor to explicitly set all fields for a ChoiceMomentData.
     * @param momentId Identifier for the ChoiceMoment.
     * @param fictionalProgress Fictional progress for this moment.
     * @param description Text description for the decision to be made.
     * @param choices HashMap of Choices available to choose.
     * @param defaultChoiceId Default Choice in case of timeout.
     * @param timeoutLengthMinutes Length of time until timeout.
     */
    public ChoiceMomentData(String momentId, ArrayList<String> fictionalProgress,
            String description, HashMap<String, Choice> choices, String defaultChoiceId,
            float timeoutLengthMinutes) {
        super(momentId, null, fictionalProgress);
        mDescription = description;
        mChoices = choices;
        mDefaultChoiceId = defaultChoiceId;
        mTimeoutLengthMinutes = timeoutLengthMinutes;
    }

    /**
     * Constructor for information from XML.
     * @param momentId Identifier for the ChoiceMoment.
     * @param fictionalProgress Fictional progress for this moment.
     * @param description Text description for the decision to be made.
     * @param defaultChoiceId Default Choice in case of timeout.
     * @param timeoutLengthMinutes Length of time until timeout.
     */
    public ChoiceMomentData(String momentId, ArrayList<String> fictionalProgress,
            String description, String defaultChoiceId, float timeoutLengthMinutes) {
        this(momentId, fictionalProgress, description, new HashMap<String, Choice>(),
                defaultChoiceId, timeoutLengthMinutes);
    }

    public String getText() {
        return mDescription;
    }

    public Choice[] getChoices() {
        return mChoices.values().toArray(new Choice[mChoices.size()]);
    }

    public Choice getChoiceById(String choiceId) {
        return mChoices.get(choiceId);
    }

    public int getNumChoices() {
        return mChoices.size();
    }

    public void addChoice(Choice choice) {
        mChoices.put(choice.getChoiceId(), choice);
    }

    public String getDefaultChoiceId() {
        return mDefaultChoiceId;
    }

    public float getTimeoutLengthMinutes() {
        return mTimeoutLengthMinutes;
    }
}


================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/MainActivity.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import android.app.Fragment;
import android.app.FragmentManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

import com.google.fpl.gim.examplegame.gui.FitnessDataDisplayFragment;
import com.google.fpl.gim.examplegame.gui.GameViews;
import com.google.fpl.gim.examplegame.gui.NotificationOptions;
import com.google.fpl.gim.examplegame.google.GoogleApiClientWrapper;
import com.google.fpl.gim.examplegame.utils.Utils;

import java.util.ArrayList;

/**
 * MainActivity class on the UI thread. It has a game handler for the game loop to execute on.
 */
public class MainActivity extends ActionBarActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    // Defines the action the BroadcastReceiver will receive.
    public static final String ENABLE_BACK
            = "com.google.fpl.gim.examplegame.ENABLE_BACK";
    public static final String MISSION_START
            = "com.google.fpl.gim.examplegame.MISSION_START";
    public static final String MISSION_END
            = "com.google.fpl.gim.examplegame.MISSION_END";
    private static final String UPDATE_FITNESS_STATS
            = "com.google.fpl.gim.examplegame.UPDATE_FITNESS_STATS";

    private MainService mMainService; // Service that runs the game logic.
    private GameViews mGameViews; // Container for all UI fragments.

    // Defines behavior when binding and unbinding mMainService.
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                                       IBinder binder) {
            if (binder == null) {
                throw new IllegalArgumentException("IBinder passed to onServiceConnected was null");
            }
            mMainService = ((MainService.MainBinder) binder).getService();
            mMainService.ConnectGoogleFitApiClient(MainActivity.this);

            // Register Intent receivers after the service is bound; this ensures that the Activity
            // is listening to intents sent from the service.
            // Register the mReceiver with an intent filter for rendering requests.
            IntentFilter filter = new IntentFilter();
            filter.addAction(ENABLE_BACK);
            filter.addAction(MISSION_START);
            filter.addAction(MISSION_END);
            registerReceiver(mReceiver, filter);

            // Render the mReceiver with an intent filter for displaying an updated num steps
            filter = new IntentFilter();
            filter.addAction(UPDATE_FITNESS_STATS);
            registerReceiver(mReceiver, filter);
        }

        public void onServiceDisconnected(ComponentName className) {
            mMainService = null;
        }
    };

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Update the step display fragment to show the current number of steps that the user
            // has taken.
            if (intent.getAction().equals(UPDATE_FITNESS_STATS)) {
                if (mMainService != null) {
                    FitnessDataDisplayFragment fitnessDataDisplayFragment
                                = mGameViews.getFitnessDataDisplayFragment();
                    if (fitnessDataDisplayFragment.isVisible()) {
                        fitnessDataDisplayFragment.setFitnessStats(
                                mMainService.getCurrentMission());
                    }
                }
            }

            // Receives an intent that requests back button to be enabled.
            if (intent.getAction().equals(ENABLE_BACK)) {
                getFragmentManager().popBackStack();
            }

            // Receives an intent that requests end summary screen to display due to a mission end.
            if (intent.getAction().equals(MISSION_START)) {
                displayFitnessStats();
            }

            // Receives an intent that requests end summary screen to display due to a mission end.
            if (intent.getAction().equals(MISSION_END)) {
                checkDisplayEndScreen();
            }
        }
    };

    @Override
    public void onDestroy() {
        Utils.logDebug(TAG, "onDestroy");
        super.onDestroy();
        if (isFinishing()) {
            // Stop the service from running in the background if the app exits.
            Intent intent = new Intent(this, MainService.class);
            intent.setPackage(getPackageName());
            stopService(intent);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        if (id == android.R.id.home) {
            getFragmentManager().popBackStack();
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == GoogleApiClientWrapper.REQUEST_CODE_OAUTH) {
            if (resultCode == RESULT_OK) {
                // If the user authenticated, try to connect again
                mMainService.userAuthenticated();
                mMainService.reconnectGoogleApi();
            } else {
                // Ideally there's a fail case here, handled by our own UX flow.
                // Keeping it empty for the sample.
            }
        }
    }

    /**
     * Responsible for transitioning from the start menu UI to the mission selection menu UI
     * @param view The view that is invoking this method.
     */
    public void onStartButtonPressed(View view) {
        mGameViews.getStartMenuFragment().onStartButtonPressed();
    }

    public void onEnterPressed(View view) {
        mGameViews.getRunSpecificationsFragment().onEnterPressed();
    }

    public void onStartMissionPressed(View view) {
        // Get user selected run and mission information.
        String missionName = getGameViews().getListOfMissionsFragment().getSelectedMissionName();
        String assetPath = getGameViews().getListOfMissionsFragment().getSelectedAssetPath();
        float missionLengthMinutes =
                getGameViews().getRunSpecificationsFragment().getSelectedMissionLengthMinutes();
        float intervalLengthMinutes =
                getGameViews().getRunSpecificationsFragment().getSelectedIntervalLengthMinutes();
        float challengePaceMinutesPerMile =
                getGameViews().getRunSpecificationsFragment().getSelectedChallengePaceMinutesPerMile();

        loadAndStartMission(assetPath, missionName, missionLengthMinutes, intervalLengthMinutes,
                challengePaceMinutesPerMile);

        // Disable the button and show that we are registering sensors.
        getGameViews().getMusicSelectionFragment().disableReadyButton();
    }

    private void displayFitnessStats() {
        String missionName = getGameViews().getListOfMissionsFragment().getSelectedMissionName();
        Fragment stepDisplayFragment = getGameViews().getFitnessDataDisplayFragment();
        ((FitnessDataDisplayFragment)stepDisplayFragment).setMissionName(missionName);
        Fragment musicSelectionFragment = getGameViews().getMusicSelectionFragment();
        getFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
                .remove(musicSelectionFragment)
                .add(R.id.container, stepDisplayFragment, GameViews.FITNESS_DATA_DISPLAY_TAG)
                .addToBackStack(null)
                .commit();

        getFragmentManager().executePendingTransactions();

        // Disable home/back button on action bar.
        displayHomeUp(false);
    }

    public GameViews getGameViews() {
        return this.mGameViews;
    }

    public void loadAndStartMission(String missionFilePath, String missionName, float missionLength,
                                    float intervalLength, float challengePaceMinutesPerMile) {
        if (mMainService != null) {
            mMainService.loadAndStartMission(missionFilePath, missionName, missionLength,
                    intervalLength, challengePaceMinutesPerMile);
        }
    }

    public void displayHomeUp(boolean display) {
        getSupportActionBar().setDisplayHomeAsUpEnabled(display);
        getSupportActionBar().setHomeButtonEnabled(display);
    }

    /**
     * Updates UI as Google Fit's connection status changes.
     */
    public void onFitStatusUpdated(boolean connected) {
        // Hint to the fragments that might need to update their displays.
        mGameViews.getStartMenuFragment().onFitStatusUpdated(connected);
        mGameViews.getEndSummaryFragment().onFitStatusUpdated(connected);

        // End a mission or pop user back to start menu if we are disconnected.
        if (!connected) {
            if (mMainService != null && mMainService.isMissionRunning()) {
                // If a mission is running, end it. No different than a mission ending on its own.
                mMainService.endMission();
            } else {
                // Jump back to the start screen.
                getFragmentManager().popBackStack(GameViews.START_MENU_TAG, 0);
            }

            if (mMainService != null) {
                // Post a notification.
                NotificationOptions notificationOptions =
                        NotificationOptions.getDefaultNotificationOptions();
                notificationOptions.setTitle(getResources().getString(
                        R.string.disconnection_notification_title));
                notificationOptions.setContent(getResources().getString(
                        R.string.disconnection_notification_content));
                notificationOptions.setNotificationId(MainService.FITNESS_DISCONNECT_NOTIFICATION_ID);
                notificationOptions.setPriorityAsHigh();
                notificationOptions.setNotificationDefaults(NotificationCompat.DEFAULT_LIGHTS);
                mMainService.postActionNotification(notificationOptions);
            }
        }
    }

    @Override
    public void onBackPressed() {
        if (mMainService != null) {
            if (!mMainService.isMissionRunning() && canPressBackButton()) {
                // If not in a mission and not at the start, function like the in-app up button.
                getFragmentManager().popBackStack();
            } else if (mMainService.isMissionRunning()) {
                // If a mission is running, end it. No different than a mission ending on its own.
                mMainService.endMission();
            } else {
                // Will call finish() and end MainActivity.
                super.onBackPressed();
            }
        }
    }

    public void setActionBarTitle(int string_res_id) {
        getSupportActionBar().setTitle(string_res_id);
    }

    public void setActionBarTitle(String title) {
        getSupportActionBar().setTitle(title);
    }

    @Override
    protected void onStart() {
        Utils.logDebug(TAG, "onStart");
        super.onStart();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Utils.logDebug(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Start service so it can run in bound and unbound state.
        Intent intent = new Intent(this, MainService.class);
        intent.setPackage(getPackageName());
        startService(intent);

        // create new fragments and initialize data
        if (savedInstanceState == null) {
            mGameViews = new GameViews();
            mGameViews.initializeFragments(this);
        } else {
            // restore old fragments and data
            mGameViews.restoreFragments(this);
        }

        displayHomeUp(canPressBackButton());

        getFragmentManager().addOnBackStackChangedListener(
                new FragmentManager.OnBackStackChangedListener() {
                    @Override
                    public void onBackStackChanged() {
                        displayHomeUp(canPressBackButton());
                    }
                });

    }

    @Override
    protected void onPause() {
        Utils.logDebug(TAG, "onPause");
        super.onPause();
        // Unregister all broadcasts before unbinding service. These will be registered again once
        // the onServiceConnected callback is called. This cannot be unregistered in the
        // onServiceConnected callback as MainActivity might be paused at that point, and it will
        // result in a crash.
        try {
            unregisterReceiver(mReceiver);
        } catch (IllegalArgumentException e) {
            Utils.logDebug(TAG, "Unable to unregister the Broadcast Receiver.");
        }
        // Unbind to allow service to run in the background.
        unbindService(mConnection);
    }

    @Override
    protected void onResume() {
        Utils.logDebug(TAG, "onResume");
        super.onResume();
        // Bind to the already running MainService so we can communicate with it.
        Intent intent = new Intent(this, MainService.class);
        intent.setPackage(getPackageName());
        bindService(intent, mConnection, Context.BIND_WAIVE_PRIORITY);
        // Handles screen being asleep during run. Will display end screen if the run is over.
        checkDisplayEndScreen();
    }

    @Override
    protected void onStop() {
        Utils.logDebug(TAG, "onStop");
        super.onStop();
    }

    /**
     * The back button should be active as long as there are fragment transactions in the back
     * stack. We always want the first fragment to be around so the back stack count needs to be
     * > 1.
     * @return Whether the back button should be active.
     */
    private boolean canPressBackButton() {
        return getFragmentManager().getBackStackEntryCount() > 1;
    }

    private void checkDisplayEndScreen() {
        if (mMainService != null) {
            if (mMainService.shouldDisplayEndScreen()) {
                displayEndScreen();
            }
        }
    }

    /**
     * Displays end run summary, with fictional and fitness results.
     */
    private void displayEndScreen() {
        getFragmentManager().popBackStack(GameViews.START_MENU_TAG, 0);
        getFragmentManager().beginTransaction()
                .replace(R.id.container, mGameViews.getEndSummaryFragment(),
                        GameViews.END_SUMMARY_TAG)
                .addToBackStack(null)
                .commit();
        getFragmentManager().executePendingTransactions();

        // Get results.
        ArrayList<String> fictionalProgress = new ArrayList<>();
        ArrayList<String> fitnessResults = new ArrayList<>();
        if (mMainService != null) {
            fictionalProgress.addAll(mMainService.getOverallFictionalProgress());
            fitnessResults.addAll(mMainService.getFitnessStatistics());
        }

        // Display results.
        mGameViews.getEndSummaryFragment().displayStats(fictionalProgress, fitnessResults);

        // Unlock first mission achievement
        if(mMainService.unlockAchievement(getString(R.string.ach_id_first_mission))) {
            Utils.logDebug(TAG, "Achievement Unlocked: First Mission");
        } else {
            Utils.logDebug(TAG, "Warning: could not unlock achievement, not connected");
        }

        if (mMainService != null) {
            mMainService.reset();
        }

        displayHomeUp(true);
    }
}


================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/MainService.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import android.app.Activity;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.widget.Toast;

import com.google.android.gms.games.Games;
import com.google.fpl.gim.examplegame.gui.GameViews;
import com.google.fpl.gim.examplegame.gui.NotificationOptions;
import com.google.fpl.gim.examplegame.google.GoogleApiClientWrapper;
import com.google.fpl.gim.examplegame.utils.MissionParseException;
import com.google.fpl.gim.examplegame.utils.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Queue;

/**
 * This is a Runnable for executing on the UI thread, and will add itself back
 * to the UI thread handler at the end of the run() function.
 *
 * While it will block the UI thread while running, it shouldn't block for that long.
 * We could always make a game thread if needed.
 */
public class MainService extends Service implements Runnable, MediaPlayer.OnCompletionListener {

    private final IBinder mBinder = new MainBinder();
    private static final String TAG = MainService.class.getSimpleName();

    private static final String CHOICE_NOTIFICATION_ACTION_1
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_1";
    private static final String CHOICE_NOTIFICATION_ACTION_2
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_2";
    private static final String CHOICE_NOTIFICATION_ACTION_3
            = "com.google.fpl.gim.examplegame.CHOICE_NOTIFICATION_ACTION_3";

    // Ids for notifications.
    public static final int CHOICE_NOTIFICATION_ID = 1;
    public static final int FITNESS_STATS_NOTIFICATION_ID = 2;
    public static final int FITNESS_DISCONNECT_NOTIFICATION_ID = 3;

    private static final Locale DEFAULT_TEXT_TO_SPEECH_LOCALE = Locale.UK;

    private Mission mMission; // The mission being played. Has reference to current game state.

    private static final long DELAY_MILLIS = 1000; // Time between updates, used as Handler delay.
    private Handler mUpdateHandler = new Handler();

    // Audio related modules.
    private TextToSpeech mTextToSpeech;
    private boolean mIsTextToSpeechReady = false;
    private AudioManager mAudioManager;
    private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener;
    private MediaPlayer mMediaPlayer;

    // A queue of Audio Uris to be played.
    private class AudioQueueItem{
        Uri mUri;
        MediaPlayer.OnCompletionListener mListener;

        AudioQueueItem(Uri uri, MediaPlayer.OnCompletionListener listener) {
            mUri = uri;
            mListener = listener;
        }

        @Override
        public boolean equals(Object o) {
            if (getClass() != o.getClass())
                return false;
            AudioQueueItem audioQueueItem = (AudioQueueItem) o;
            return (mUri.equals(audioQueueItem.mUri));
        }
    }
    private Queue<AudioQueueItem> mAudioQueue = new LinkedList<>();

    private enum State {
        UNINITIALIZED,
        MISSION_LOADED,
        MISSION_RUNNING,
        END_SCREEN
    }
    private State mState = State.UNINITIALIZED;

    private GoogleApiClientWrapper mGoogleApiClientWrapper =
            new GoogleApiClientWrapper(); // Container for the GoogleApiClient

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (mMission != null && mMission.getMissionData().getCurrentMoment() != null) {
                // Choice moment handles choice selection.
                ((ChoiceMoment) mMission.getMissionData().getCurrentMoment())
                        .onReceive(context, intent);
            }
        }
    };

    /**
     * This is the main game loop. Whenever it is done, it adds itself back to the handler.
     */
    @Override
    public void run() {
        if (mState == State.MISSION_LOADED || mState == State.MISSION_RUNNING) {
            // This is where we can call the game state and the game logic.
            update();
        }
        mUpdateHandler.postDelayed(this, DELAY_MILLIS);
    }

    public void userAuthenticated() {
        mGoogleApiClientWrapper.userAuthenticated();
    }

    public void reconnectGoogleApi() {
        Utils.logDebug(TAG, "Reconnecting to Google API.");
        mGoogleApiClientWrapper.connect();
    }

    public void ConnectGoogleFitApiClient(Activity activity) {
        mGoogleApiClientWrapper.buildGoogleApiClient(activity);
        mGoogleApiClientWrapper.connect();
    }

    /**
     * Unlock a Play Games achievement.
     *
     * @param achievementId the ID of the achievement from the Google Play Developer Console,
     * @return true if Achievement unlocked, false otherwise.
     */
    public boolean unlockAchievement(String achievementId) {
        if (mGoogleApiClientWrapper.isSignedIn()) {
            Games.Achievements.unlock(mGoogleApiClientWrapper.getGoogleApiClient(), achievementId);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Loads and begins a mission.
     */
    public void loadAndStartMission(String missionFilePath, String missionName,
                                    float missionLengthMinutes, float intervalLengthMinutes,
                                    float challengePaceMinutesPerMile) {
        if (!canEnterState(State.MISSION_LOADED)) {
            return;
        }
        MissionData data = new MissionData(missionName, missionFilePath, missionLengthMinutes,
                intervalLengthMinutes, challengePaceMinutesPerMile);
        mMission = new Mission(data);
        mMission.setService(this);

        // Open an InputStream from the given missionFileName.
        InputStream missionStream;
        try {
            missionStream = getAssets().open(missionFilePath);
        } catch (IOException e) {
            e.printStackTrace();
            requestReselection();
            return;
        }

        // Load the Moments.
        try {
            mMission.readMoments(missionStream);
        } catch (MissionParseException e) {
            e.printStackTrace();
            requestReselection();
            return;
        }

        try {
            missionStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        startMission();
    }

    /**
     * Starts a mission.
     */
    private void startMission() {
        setAndInitNextState(State.MISSION_LOADED);
    }

    /**
     * Ends a mission by halting updates.
     */
    public void endMission() {
        setAndInitNextState(State.END_SCREEN);
    }

    @Override
    public void onCreate() {
        // The service is being created.
        Utils.logDebug(TAG, "onCreate");
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(CHOICE_NOTIFICATION_ACTION_1);
        intentFilter.addAction(CHOICE_NOTIFICATION_ACTION_2);
        intentFilter.addAction(CHOICE_NOTIFICATION_ACTION_3);
        registerReceiver(mReceiver, intentFilter);

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

        // Determines the behavior for handling Audio Focus surrender.
        mAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
            @Override
            public void onAudioFocusChange(int focusChange) {
                if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
                        || focusChange == AudioManager.AUDIOFOCUS_LOSS) {

                    if (mTextToSpeech.isSpeaking()) {
                        mTextToSpeech.setOnUtteranceProgressListener(null);
                        mTextToSpeech.stop();
                    }

                    if (mMediaPlayer.isPlaying()) {
                        mMediaPlayer.stop();
                    }

                    // Abandon Audio Focus, if it's requested elsewhere.
                    mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);

                    // Restart the current moment if AudioFocus was lost. Since AudioFocus is only
                    // requested away from this application if this application was using it,
                    // only Moments that play sound will restart in this way.
                    if (mMission != null) {
                        mMission.restartMoment();
                    }
                }
            }
        };

        // Asynchronously prepares the TextToSpeech.
        mTextToSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status == TextToSpeech.SUCCESS) {
                    // Check if language is available.
                    switch (mTextToSpeech.isLanguageAvailable(DEFAULT_TEXT_TO_SPEECH_LOCALE)) {
                        case TextToSpeech.LANG_AVAILABLE:
                        case TextToSpeech.LANG_COUNTRY_AVAILABLE:
                        case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
                            Utils.logDebug(TAG, "TTS locale supported.");
                            mTextToSpeech.setLanguage(DEFAULT_TEXT_TO_SPEECH_LOCALE);
                            mIsTextToSpeechReady = true;
                            break;
                        case TextToSpeech.LANG_MISSING_DATA:
                            Utils.logDebug(TAG, "TTS missing data, ask for install.");
                            Intent installIntent = new Intent();
                            installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
                            startActivity(installIntent);
                            break;
                        default:
                            Utils.logDebug(TAG, "TTS local not supported.");
                            break;
                    }
                }
            }
        });

        mMediaPlayer = new MediaPlayer();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        Utils.logDebug(TAG, "onStartCommand");
        return Service.START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        Utils.logDebug(TAG, "onBind");

        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        Utils.logDebug(TAG, "onUnbind");
        return true;
    }

    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
        Utils.logDebug(TAG, "onRebind");

        if (mMission != null) {
            mMission.onRebind();
        }
    }

    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed.
        Utils.logDebug(TAG, "onDestroy");
        mGoogleApiClientWrapper.disconnect();
        if (mIsTextToSpeechReady) {
            mTextToSpeech.shutdown();
        }
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
        }
        mUpdateHandler.removeCallbacks(this);
        unregisterReceiver(mReceiver);
        if (mMission != null) {
            mMission.cleanup();
        }
    }

    /**
     * Callback listener for MediaPlayer.
     * @param player MediaPlayer instance.
     */
    @Override
    public void onCompletion(MediaPlayer player) {
        endPlayback();
    }

    /**
     * A Binder for the connection between MainService and MainActivity that allows MainActivity
     * to get the running instance of MainService.
     */
    public class MainBinder extends Binder {
        MainService getService() {
            return MainService.this;
        }
    }

    /**
     * Create a notification action that upon selection triggers the provided action.
     * @param intent Intent to carry out when the notification action is selected.
     * @param actionIconResourceId Resource Id of icon for this action.
     * @param actionDescription Name of this action.
     * @return A notification action that can be selected.
     */
    public NotificationCompat.Action makeNotificationAction(Intent intent,
            int actionIconResourceId, String actionDescription) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return new NotificationCompat.Action(actionIconResourceId,
                actionDescription, pendingIntent);
    }

    /**
     * Builds and posts a notification from a set of options.
     * @param options The options to build the notification.
     */
    public void postActionNotification(NotificationOptions options) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(options.getSmallIconResourceId());
        builder.setContentTitle(options.getTitle());
        builder.setContentText(options.getContent());
        builder.setDefaults(options.getNotificationDefaults());
        builder.setPriority(options.getNotificationPriority());
        builder.setVibrate(options.getVibratePattern());
        if (options.getActions() != null) {
            for (NotificationCompat.Action action : options.getActions()) {
                builder.addAction(action);
            }
        }
        NotificationManagerCompat notificationManager =
                NotificationManagerCompat.from(this);
        notificationManager.notify(options.getNotificationId(), builder.build());
    }

    public boolean isMissionRunning() {
        return mState == State.MISSION_RUNNING;
    }

    public boolean shouldDisplayEndScreen() {
        return mState == State.END_SCREEN;
    }

    public void reset() {
        setAndInitNextState(State.UNINITIALIZED);
    }

    public ArrayList<String> getOverallFictionalProgress() {
        return mMission.getOverallFictionalProgress();
    }

    /**
     * Gets fitness statistics for the last played game.
     * @return An array list of fitness statistics to display.
     */
    public ArrayList<String> getFitnessStatistics() {
        return getCurrentMission().getFitnessStatistics();
    }

    public Mission getCurrentMission() {
        return this.mMission;
    }

    /**
     * Queue a sound into the audio queue.
     * @param uri The Uri of the sound.
     * @param listener The listener to the sound. This is usually MainService but can be overridden.
     */
    public void queueSound(Uri uri, MediaPlayer.OnCompletionListener listener) {
        mAudioQueue.offer(new AudioQueueItem(uri, listener));
    }

    /**
     * Removes the first instance of a sound from the audio queue.
     * @param uri Uri of the item to be removed.
     */
    public void dequeueSound(Uri uri) {
        mAudioQueue.remove(new AudioQueueItem(uri, null));
    }

    /**
     * Obtain audio focus for the application. This also checks if we are currently playing any
     * other audio clips, so it checks for "audio focus" within the app.
     * @return True if audio focus is obtained. False otherwise.
     */
    public boolean obtainAudioFocus() {
        if (mMediaPlayer.isPlaying() || mTextToSpeech.isSpeaking()) {
            return false;
        }

        int result = mAudioManager.requestAudioFocus(
                mAudioFocusChangeListener, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        return (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    }

    /**
     * End audio playback, and abandon audio focus.
     */
    public void endPlayback() {
        mMediaPlayer.reset();
        mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
    }

    protected TextToSpeech getTextToSpeech() { return mIsTextToSpeechReady? mTextToSpeech : null; }

    /**
     * Checks if the state transition is valid.
     * @param state State to transition to.
     * @return True if the transition is valid, false if not.
     */
    private boolean canEnterState(State state) {
        if (mState == state) {
            return false;
        }

        boolean canEnterState = true;
        switch (state) {
            case UNINITIALIZED:
                break;
            case MISSION_LOADED:
                if (mState == State.MISSION_RUNNING) {
                    canEnterState = false;
                    Utils.logDebug(TAG,
                            "Can not enter MISSION_LOADED state from MISSION_RUNNING state.");
                }
                break;
            case MISSION_RUNNING:
                if (mState != State.MISSION_LOADED) {
                    canEnterState = false;
                    Utils.logDebug(TAG,
                            "Can only enter MISSION_RUNNING state from MISSION_LOADED state.");
                }
                break;
            case END_SCREEN:
                if (mState != State.MISSION_RUNNING) {
                    canEnterState = false;
                    Utils.logDebug(TAG,
                            "Can only enter END_SCREEN state from MISSION_RUNNING state.");
                }
                break;
        }
        return canEnterState;
    }

    /**
     * Sets the next state if possible.
     * @param state State to transition to.
     */
    private void setAndInitNextState(State state) {
        if (!canEnterState(state)) {
            return;
        }
        mState = state;
        switch (mState) {
            case UNINITIALIZED:
                break;
            case MISSION_LOADED:
                mMission.prepare(mGoogleApiClientWrapper);
                mUpdateHandler.post(this);
                break;
            case MISSION_RUNNING:
                mMission.start();
                broadcastStart();
                break;
            case END_SCREEN:
                mUpdateHandler.removeCallbacks(this);
                mMission.cleanup();
                broadcastEnd();
                break;
        }
    }

    private void update() {
        if (mState == State.MISSION_LOADED && isReady()) {
            setAndInitNextState(State.MISSION_RUNNING);
        }
        if (mState == State.MISSION_RUNNING) {
            mMission.update();

            if (mMission.isDone()) {
                endMission();
            }
        }

        // Consume the audio queue if it's not empty and if we are able to obtain audio focus.
        if (!mAudioQueue.isEmpty() && obtainAudioFocus()) {
            playFirstInQueue();
        }
    }

    /**
     * Play the first item in the audio queue.
     */
    private void playFirstInQueue() {
        AudioQueueItem queueItem = mAudioQueue.poll();
        try {
            mMediaPlayer.setDataSource(this, queueItem.mUri);
        } catch (IOException e) {
            e.printStackTrace();
            // Data source does not exist. Skip playback.
            endPlayback();
            return;
        }
        mMediaPlayer.setOnCompletionListener(queueItem.mListener);
        try {
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
            // Error in reading the data source. Skip playback.
            endPlayback();
            return;
        }
        mMediaPlayer.start();
    }

    /**
     * @return True if asynchronous preparations are all done, so that a mission can be started.
     */
    private boolean isReady() {
        return mIsTextToSpeechReady && mGoogleApiClientWrapper.isClientReady();
    }

    /**
     * Broadcast to MainActivity to enable back navigation.
     */
    private void enableBackNavigation() {
        sendBroadcast(new Intent(MainActivity.ENABLE_BACK));
    }

    /**
     * Display a Toast that requests user to reselect their mission.
     */
    private void requestReselection() {
        Toast.makeText(this, "Mission load failure. Select again.", Toast.LENGTH_SHORT).show();
        enableBackNavigation();
    }

    /**
     * Broadcast to MainActivity that mission has started.
     */
    private void broadcastStart() {
        sendBroadcast(new Intent(MainActivity.MISSION_START));
    }

    /**
     * Broadcast to MainActivity that mission has ended.
     */
    private void broadcastEnd() {
        sendBroadcast(new Intent(MainActivity.MISSION_END));
    }
}


================================================
FILE: app/src/main/java/com/google/fpl/gim/examplegame/Mission.java
================================================
/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.fpl.gim.examplegame;

import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;

import com.google.android.gms.fitness.data.DataPoint;
import com.google.android.gms.fitness.data.DataType;
import com.google.android.gms.fitness.data.Field;
import com.google.android.gms.fitness.data.Value;
import com.google.android.gms.fitness.request.OnDataPointListener;
import com.google.android.gms.fitness.request.SensorRequest;
import com.google.fpl.gim.examplegame.gui.NotificationOptions;
import com.google.fpl.gim.examplegame.google.FitDataTypeSetting;
import com.google.fpl.gim.examplegame.google.GoogleApiClientWrapper;
import com.google.fpl.gim.examplegame.utils.MissionParseException;
import com.google.fpl.gim.examplegame.utils.MissionParser;
import com.google.fpl.gim.examplegame.utils.Utils;

import java.io.InputStream;
import java.util.ArrayList;

/**
 * A mission is a complete gameplay during which the exercising user will be challenged to defeat
 * fictional pursuers.  The user will have a weapon that can only be charged by running faster.
 */
public class Mission implements OnDataPointListener {

    private static final String TAG = Mission.class.getSimpleName();

    private static final FitDataTypeSetting[] TRACKED_DATA_TYPES = {
        new FitDataTypeSetting(
                true /* isRequired */, DataType.TYPE_STEP_COUNT_DELTA, 1 /* samplingRateSeconds */,
                SensorRequest.ACCURACY_MODE_DEFAULT),
        new FitDataTypeSetting(
                false /* isRequired */, DataType.TYPE_SPEED, 1 /* samplingRateSeconds */,
                SensorRequest.ACCURACY_MODE_HIGH),
    };

    private static final String UPDATE_FITNESS_STATS
            = "com.google.fpl.gim.examplegame.UPDATE_FITNESS_STATS";

    // ID that signifies the moment whose next is this is an ending moment.
    private static final String DEFAULT_END_ID = null;
    private MissionData mData;

    // Access to MainService to obtain and use Android Context.
    private MainService mService;
    private boolean mIsDone = false;
    private boolean mIsStarted = false;

    // Access to GoogleFitApiClient for fit data
    private GoogleApiClientWrapper mGoogleApiClientWrapper;

    // Fitness stats for the mission as a whole.
    private int mTotalNumStepsTaken = 0;
    private int mTotalNumIntervalsCompleted = 0;
    private long mMissionStartTimeNanos;

    // Fitness stats for a small portion of the mission.
    private int mNumStepsSinceBeginningOfSample = 0;
    private long mSampleStartTimeNanos;
    private float mCurrentAverageMinutesPerMile = 0f;
    private static final float AVERAGE_SPEED_SAMPLE_RATE_SECONDS = 10.0f;
    private static final float LENGTH_OF_RUNNING_STRIDE_FEET = 5.5f;
    private static final float MAXIMUM_MINUTES_PER_MILE = 1000f;

    private boolean mIsAtChallengePace = false;
    private long mTimestampStartOfChallengePaceNanos;

    private static final String AT_CHALLENGE_PACE_RESOURCE
            = "android.resource://com.google.fpl.gim.examplegame/raw/atchallengepace";
    private static final String NO_LONGER_AT_CHALLENGE_PACE_RESOURCE
            = "android.resource://com.google.fpl.gim.examplegame/raw/nolongeratchallengepace";
    private static final String WEAPON_CHARGED_RESOURCE
            = "android.resource://com.google.fpl.gim.examplegame/raw/weaponcharged";
    private final Uri AT_CHALLENGE_PACE_URI = Uri.parse(AT_CHALLENGE_PACE_RESOURCE);
    private final Uri NO_LONGER_AT_CHALLENGE_PACE_URI =
            Uri.parse(NO_LONGER_AT_CHALLENGE_PACE_RESOURCE);
    private final Uri WEAPON_CHARGED_URI = Uri.parse(WEAPON_CHARGED_RESOURCE);

    // The current time represented in nanoseconds.
    private long mNowNanos;

    private boolean mIsWeaponCharged = false;
    private float mLastWeaponCharge;
    private int mNumEnemiesDefeated = 0;

    private ArrayList<String> mOverallFictionalProgress = new ArrayList<>();

    public Mission(MissionData data) {
        this.mData = data;
    }

    /**
     * Makes the moment referred to by nextMomentId the current Moment. Checks if the game should
     * end by checking the nextMomentId.
     * @param nextMomentId The ID of the moment to make the current moment.
     */
    public void changeCurrentMoment(String nextMomentId, long now) {
        if (nextMomentId == DEFAULT_END_ID || nextMomentId.equals(DEFAULT_END_ID)) {
            mIsDone = true;
            return;
        }

        mData.setCurrentMomentId(nextMomentId);
        mData.getCurrentMoment().start(now);
    }

    /**
     * Loads the moments read from an xml file that define a mission into the mission data.
     * @param missionStream An input stream for an xml file.
     * @throws com.google.fpl.gim.examplegame.utils.MissionParseException Thrown when file parsing failed due to parser configuration,
     * input exceptions, or incorrectly structured file.
     */
    public void readMoments(InputStream missionStream) throws MissionParseException {
        // Exceptions may be thrown from MissionParser.parseMission
        MissionParser.parseMission(missionStream, this);
    }

    public void start() {
        mNowNanos = System.nanoTime();
        mMissionStartTimeNanos = mNowNanos;
        changeCurrentMoment(mData.getFirstMomentId(), mNowNanos);
        mIsStarted = true;
        mSampleStartTimeNanos = mNowNanos;
        mLastWeaponCharge = 0f;
        // Create the notification to notify the user of their current fitness statistics.
        postFitnessNotification(getFitnessNotificationTitle());
    }

    public void cleanup() {
        Utils.logDebug(TAG, mOverallFictionalProgress.toString());

        // Clean up the current moment.
        if (mData.getCurrentMoment() != null) {
            mData.getCurrentMoment().end();
        }

        NotificationManager notificationManager = (NotificationManager) getService()
                .getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(MainService.CHOICE_NOTIFICATION_ID);
        notificationManager.cancel(MainService.FITNESS_STATS_NOTIFICATION_ID);

        if (mGoogleApiClientWrapper != null) {
            mGoogleApiClientWrapper.endFitDataSession(TRACKED_DATA_TYPES, this);
            mGoogleApiClientWrapper = null;
        }
    }

    public void addMoment(String id, Moment moment) {
        this.mData.addMoment(id, moment);
    }

    public void setFirstMomentId(String firstMomentId) {
        this.mData.setFirstMomentId(firstMomentId);
    }

    public void update() {
        mNowNanos = System.nanoTime();

        // Calculate average speed at a consistent time interval.
        float timePassedSeconds = Utils.nanosToSeconds(mNowNanos - mSampleStartTimeNanos);
        if (timePassedSeconds >= AVERAGE_SPEED_SAMPLE_RATE_SECONDS) {
            calculateAverageMinutesPerMile();
        }

        Moment currentMoment = mData.getCurrentMoment();
        currentMoment.update(mNowNanos);
        if (currentMoment.isDone()) {
            mOverallFictionalProgress.addAll(currentMoment.getFictionalProgress());
            currentMoment.end();
            changeCurrentMoment(currentMoment.getNextMomentId(), mNowNanos);
        }

        if (mLastWeaponCharge != getWeaponChargedPercentage()) {
            mLastWeaponCharge = getWeaponChargedPercentage();
            // Create the notification to notify the user of their current fitness statistics.
            postFitnessNotification(getFitnessNotificationTitle());
        }

        Intent updateFitnessStatsIntent = new Intent();
        updateFitnessStatsIntent.setAction(UPDATE_FITNESS_STATS);
        getService().sendBroadcast(updateFitnessStatsIntent);
    }

    public void postFitnessNotification(String title) {
        NotificationOptions notificationOptions =
                NotificationOptions.getDefaultNotificationOptions();
        notificationOptions.setTitle(title);
        notificationOptions.setContent(getService()
                .getString(R.string.weapon_status_notification_text));
        notificationOptions.setNotificationId(MainService.FITNESS_STATS_NOTIFICATION_ID);
        notificationOptions.setPriorityAsHigh();
        notificationOptions.setNotificationDefaults(NotificationCompat.DEFAULT_LIGHTS);
        getService().postActionNotification(notificationOptions);
    }

    public void setService(MainService service) {
        this.mService = service;
    }

    public MainService getService() {
        return this.mService;
    }

    public boolean isDone() {
        return mIsDone;
    }

    public void prepare(GoogleApiClientWrapper googleApiClientWrapper) {
        Utils.logDebug(TAG, "Mission prepared.");

        // Start collecting Google Fit data
        mGoogleApiClientWrapper = googleApiClientWrapper;
        mGoogleApiClientWrapper.startFitDataSession(
                TRACKED_DATA_TYPES, getMissionData().getMissionName(), this);
    }

    public boolean isWeaponCharged() {
        return mIsWeaponCharged;
    }

    public MissionData getMissionData() {
        return this.mData;
    }

    /**
     * Restarts the current moment with a time delay.
     */
    public void restartMoment() {
        if (mData.getCurrentMoment() != null) {
            mData.getCurrentMoment().restartWithDelay(mNowNanos, 0f);
        }
    }

    public void applyOutcome(Outcome outcome) {
        if (outcome.numEnemiesDefeatedIncremented()) {
            mNumEnemiesDefeated++;
        }
        if (outcome.weaponChargeDepleted()) {
            mIsWeaponCharged = false;
        }
    }

    public ArrayList<String> getOverallFictionalProgress() {
        return mOverallFictionalProgress;
    }

    @Override
    public void onDataPoint(DataPoint dataPoint) {
        // If we get data before the mission has started, discard them.
        if (!mIsStarted) {
            return;
        }
        DataType dataType = dataPoint.getDataType();
        for (Field field : dataType.getFields()) {
            Value val = dataPoint.getValue(field);
            if (dataType.equals(DataType.TYPE_STEP_COUNT_DELTA)) {
                onStepTaken(val.asInt());
            } else if (dataType.equals(DataType.TYPE_SPEED)) {
                // Data comes in as meters per second, have to convert to minutes per mile.
                float speedMetersPerSeconds = val.asFloat();
                updateChallengePace(Utils.metersPerSecondToMinutesPerMile(speedMetersPerSeconds));
            }
        }
    }

    public void onStepTaken(int steps) {
        mNumStepsSinceBeginningOfSample += steps;
        mTotalNumStepsTaken += steps;
        Utils.logDebug(TAG,
                "Fit data update. You have now taken " + mTotalNumStepsTaken + " steps.");

        // Update UI whenever a step is taken
        Intent updateFitnessStatsIntent = new Intent();
        updateFitnessStatsIntent.setAction(UPDATE_FITNESS_STATS);
        getService().sendBroadcast(updateFitnessStatsIntent);
    }

    public int getNumSteps() {
        return this.mTotalNumStepsTaken;
    }

    public float getMinutesPerMile() {
        return this.mCurrentAverageMinutesPerMile;
    }

    public int getNumMinutesExercised() {
        long timePassedNanos = mNowNanos - mMissionStartTimeNanos;
        int timePassedSeconds = (int) Utils.nanosToSeconds(timePassedNanos);
        int timePassedMinutes = timePassedSeconds / Utils.MINUTES_TO_SECONDS_SCALE;
        return timePassedMinutes;
    }

    public int getNumSecondsExercised() {
        long timePassedNanos = mNowNanos - mMissionStartTimeNanos;
        int timePassedSeconds = (int) Utils.nanosToSeconds(timePassedNanos);
        return timePassedSeconds % Utils.MINUTES_TO_SECONDS_SCALE;
    }

    public int getWeaponChargedPercentage() {
        if (mIsWeaponCharged) {
            return 100;
        }
        if (!mIsAtChallengePace) {
            return 0;
        }
        long timeAtChallengePaceNanos = mNowNanos - mTimestampStartOfChallengePaceNanos;
        float timeAtChallengePaceMinutes = Utils.nanosToMinutes(timeAtChallengePaceNanos);
        float weaponChargedPercentage = timeAtChallengePaceMinutes
                / mData.getLengthOfIntervalMinutes() * 100;
        return (int) weaponChargedPercentage;
    }

    public float getChallengePace() {
        return mData.getChallengePaceMinutesPerMile();
    }

    public void onRebind() {
        // Update UI after app wakes up (after the A
Download .txt
gitextract_6xtmc2to/

├── .gitignore
├── .gitmodules
├── .idea/
│   ├── .name
│   ├── compiler.xml
│   ├── copyright/
│   │   ├── Apache_2_0.xml
│   │   └── profiles_settings.xml
│   ├── encodings.xml
│   ├── gradle.xml
│   ├── libraries/
│   │   ├── appcompat_v7_21_0_3.xml
│   │   ├── junit_3_8.xml
│   │   ├── play_services_6_1_71.xml
│   │   ├── support_annotations_21_0_3.xml
│   │   └── support_v4_21_0_3.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── scopes/
│   │   └── scope_settings.xml
│   └── vcs.xml
├── CONTRIBUTING
├── ExampleGame.iml
├── LICENSE
├── app/
│   ├── app.iml
│   ├── build.gradle
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── google/
│       │               └── fpl/
│       │                   └── gim/
│       │                       └── examplegame/
│       │                           └── MissionParseTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   ├── legacy_missions/
│           │   │   ├── choice_mission.xml
│           │   │   ├── mission.xml
│           │   │   ├── sfx mission.xml
│           │   │   ├── spoken_plus_timer_mission.xml
│           │   │   ├── texttospeechmission.xml
│           │   │   └── timermission.xml
│           │   └── missions/
│           │       ├── 01_sample_mission_1.xml
│           │       ├── 02_sample_mission_2.xml
│           │       ├── ex_01_timer_moment.xml
│           │       ├── ex_02_spoken_text_moment.xml
│           │       ├── ex_03_choice_moment.xml
│           │       ├── ex_04_sfx_moment.xml
│           │       ├── ex_05_broken_timer_moment.xml
│           │       └── ex_06_long_timer_mission.xml
│           ├── java/
│           │   └── com/
│           │       └── google/
│           │           └── fpl/
│           │               └── gim/
│           │                   └── examplegame/
│           │                       ├── Choice.java
│           │                       ├── ChoiceMoment.java
│           │                       ├── ChoiceMomentData.java
│           │                       ├── MainActivity.java
│           │                       ├── MainService.java
│           │                       ├── Mission.java
│           │                       ├── MissionData.java
│           │                       ├── Moment.java
│           │                       ├── MomentData.java
│           │                       ├── Outcome.java
│           │                       ├── SfxMoment.java
│           │                       ├── SfxMomentData.java
│           │                       ├── SpokenTextMoment.java
│           │                       ├── SpokenTextMomentData.java
│           │                       ├── TimerMoment.java
│           │                       ├── TimerMomentData.java
│           │                       ├── google/
│           │                       │   ├── FitDataTypeSetting.java
│           │                       │   ├── FitResultCallback.java
│           │                       │   └── GoogleApiClientWrapper.java
│           │                       ├── gui/
│           │                       │   ├── EndSummaryFragment.java
│           │                       │   ├── FitnessDataDisplayFragment.java
│           │                       │   ├── GameViews.java
│           │                       │   ├── MissionSelectionFragment.java
│           │                       │   ├── MusicSelectionFragment.java
│           │                       │   ├── NotificationOptions.java
│           │                       │   ├── RunSpecificationSelectionFragment.java
│           │                       │   └── StartMenuFragment.java
│           │                       └── utils/
│           │                           ├── MissionParseException.java
│           │                           ├── MissionParser.java
│           │                           └── Utils.java
│           └── res/
│               ├── anim/
│               │   ├── slide_in_right.xml
│               │   └── slide_out_left.xml
│               ├── drawable/
│               │   └── weapon_charge_progress.xml
│               ├── layout/
│               │   ├── activity_main.xml
│               │   ├── end_screen.xml
│               │   ├── menu_list_item.xml
│               │   ├── menu_mission_list.xml
│               │   ├── menu_music_selection.xml
│               │   ├── menu_run_specifications.xml
│               │   ├── menu_start.xml
│               │   ├── placeholder_fragment.xml
│               │   └── step_display.xml
│               ├── menu/
│               │   └── main.xml
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── values-w820dp/
│                   └── dimens.xml
├── build.gradle
├── docs/
│   ├── generate_docs.py
│   └── src/
│       ├── contributing.md
│       ├── doxyfile
│       ├── doxygen_layout.xml
│       ├── index.md
│       └── programmers_guide/
│           ├── assets.md
│           ├── audio.md
│           ├── building.md
│           ├── core.md
│           ├── gameplay.md
│           ├── google_api.md
│           ├── mission.md
│           └── overview.md
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── prototype.iml
├── readme.md
└── settings.gradle
Download .txt
SYMBOL INDEX (406 symbols across 32 files)

FILE: app/src/androidTest/java/com/google/fpl/gim/examplegame/MissionParseTest.java
  class MissionParseTest (line 32) | public class MissionParseTest extends TestCase {
    method setUp (line 39) | public void setUp() {
    method tearDown (line 50) | public void tearDown() {
    method testTimerMomentConstruction (line 57) | public void testTimerMomentConstruction() {
    method testChoiceMomentConstruction (line 89) | public void testChoiceMomentConstruction() {
    method testSfxMomentConstruction (line 165) | public void testSfxMomentConstruction() {
    method testSpokenTextMomentConstruction (line 196) | public void testSpokenTextMomentConstruction() {
    method testFourMomentConstruction (line 229) | public void testFourMomentConstruction() {
    method testMomentWithInvalidTypeErrorHandling (line 323) | public void testMomentWithInvalidTypeErrorHandling() {
    method testMomentWithNoTypeErrorHandling (line 356) | public void testMomentWithNoTypeErrorHandling() {
    method testTimerMomentMissingNextMomentMission (line 387) | public void testTimerMomentMissingNextMomentMission() {
    method testMissionNameConstruction (line 420) | public void testMissionNameConstruction() {
    method testMissingMissionNameHandling (line 443) | public void testMissingMissionNameHandling() {
    method testFictionalProgressSpokenTextMomentParsing (line 466) | public void testFictionalProgressSpokenTextMomentParsing() {
    method testFictionalProgressTimerMomentParsing (line 499) | public void testFictionalProgressTimerMomentParsing() {
    method testFictionalProgressSfxMomentParsing (line 532) | public void testFictionalProgressSfxMomentParsing() {
    method testFictionalProgressChoiceMomentParsing (line 566) | public void testFictionalProgressChoiceMomentParsing() {
    method testFictionalProgressErrorHandling (line 632) | public void testFictionalProgressErrorHandling() {
    method testChoiceMissingIconErrorHandling (line 665) | public void testChoiceMissingIconErrorHandling() {
    method testChoiceEmptyIconErrorHandling (line 721) | public void testChoiceEmptyIconErrorHandling() {
    method createStartMissionXml (line 784) | private String createStartMissionXml(String startMomentId, String miss...
    method createEndMissionXml (line 795) | private String createEndMissionXml() {
    method createStartMomentXml (line 806) | private String createStartMomentXml(String momentType, String momentId) {
    method createEndMomentXml (line 816) | private String createEndMomentXml() {
    method createTimerMomentXml (line 832) | private String createTimerMomentXml(String momentId, String nextMomentId,
    method createNextMomentXml (line 854) | private String createNextMomentXml(String nextMomentId) {
    method createStartChoiceMomentXml (line 878) | private String createStartChoiceMomentXml(String momentId, double time...
    method createChoiceXml (line 913) | private String createChoiceXml(String choiceId, String choiceDescripti...
    method createEndChoiceMomentXml (line 937) | private String createEndChoiceMomentXml() {
    method createSpokenTextMomentXml (line 953) | private String createSpokenTextMomentXml(String momentId, String nextM...
    method createSfxMomentXml (line 978) | private String createSfxMomentXml(String momentId, String nextMomentId...
    method createLengthMinutesXml (line 996) | private String createLengthMinutesXml(double lengthMinutes) {
    method createFictionalProgressXml (line 1006) | private String createFictionalProgressXml(String progressDescription) {
    method createIconXML (line 1016) | private String createIconXML(String iconResourceName) {
    method createSpokenTextMomentWithFictionalProgressXML (line 1035) | private String createSpokenTextMomentWithFictionalProgressXML(String m...
    method createTimerMomentWithFictionalProgressXML (line 1061) | private String createTimerMomentWithFictionalProgressXML(String moment...
    method createSfxMomentWithFictionalProgressXML (line 1089) | private String createSfxMomentWithFictionalProgressXML(String momentId...
    method createStartChoiceMomentWithFictionalProgressXML (line 1122) | private String createStartChoiceMomentWithFictionalProgressXML (String...
    method createChoiceWithFictionalProgressXML (line 1155) | private String createChoiceWithFictionalProgressXML(String choiceId, S...

FILE: app/src/main/java/com/google/fpl/gim/examplegame/Choice.java
  class Choice (line 25) | public class Choice {
    method Choice (line 41) | public Choice(String choiceId, String text, String nextMomentId, Outco...
    method getChoiceId (line 53) | public String getChoiceId() {
    method getDescription (line 57) | public String getDescription() {
    method getNextMomentId (line 61) | public String getNextMomentId() {
    method getOutcome (line 65) | public Outcome getOutcome() {
    method getFictionalProgress (line 69) | public ArrayList<String> getFictionalProgress() {
    method requiresChargedWeapon (line 73) | public boolean requiresChargedWeapon() {
    method getDrawableResourceName (line 77) | public String getDrawableResourceName() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMoment.java
  class ChoiceMoment (line 32) | public class ChoiceMoment extends Moment {
    method ChoiceMoment (line 54) | public ChoiceMoment (Mission mission, ChoiceMomentData data) {
    method update (line 59) | public void update(long nowNanos) {
    method start (line 70) | @Override
    method end (line 124) | @Override
    method getNextMomentId (line 135) | @Override
    method restart (line 144) | @Override
    method getMomentData (line 149) | public ChoiceMomentData getMomentData() {
    method setStartTimeNanos (line 153) | public void setStartTimeNanos(long startTimeNanos) {
    method hasTimeToMakeChoiceExpired (line 157) | public boolean hasTimeToMakeChoiceExpired(long nowNanos) {
    method onReceive (line 162) | public void onReceive(Context context, Intent intent) {
    method selectChoice (line 171) | public synchronized void selectChoice(String choiceId) {
    method dismissNotification (line 180) | public void dismissNotification() {
    method noChoiceSelectedYet (line 186) | public boolean noChoiceSelectedYet() {
    method getFictionalProgress (line 190) | @Override

FILE: app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMomentData.java
  class ChoiceMomentData (line 25) | public class ChoiceMomentData extends MomentData {
    method ChoiceMomentData (line 45) | public ChoiceMomentData(String momentId, ArrayList<String> fictionalPr...
    method ChoiceMomentData (line 63) | public ChoiceMomentData(String momentId, ArrayList<String> fictionalPr...
    method getText (line 69) | public String getText() {
    method getChoices (line 73) | public Choice[] getChoices() {
    method getChoiceById (line 77) | public Choice getChoiceById(String choiceId) {
    method getNumChoices (line 81) | public int getNumChoices() {
    method addChoice (line 85) | public void addChoice(Choice choice) {
    method getDefaultChoiceId (line 89) | public String getDefaultChoiceId() {
    method getTimeoutLengthMinutes (line 93) | public float getTimeoutLengthMinutes() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/MainActivity.java
  class MainActivity (line 46) | public class MainActivity extends ActionBarActivity {
    method onServiceConnected (line 64) | public void onServiceConnected(ComponentName className,
    method onServiceDisconnected (line 87) | public void onServiceDisconnected(ComponentName className) {
    method onReceive (line 93) | @Override
    method onDestroy (line 125) | @Override
    method onCreateOptionsMenu (line 137) | @Override
    method onOptionsItemSelected (line 144) | @Override
    method onActivityResult (line 158) | @Override
    method onStartButtonPressed (line 176) | public void onStartButtonPressed(View view) {
    method onEnterPressed (line 180) | public void onEnterPressed(View view) {
    method onStartMissionPressed (line 184) | public void onStartMissionPressed(View view) {
    method displayFitnessStats (line 202) | private void displayFitnessStats() {
    method getGameViews (line 220) | public GameViews getGameViews() {
    method loadAndStartMission (line 224) | public void loadAndStartMission(String missionFilePath, String mission...
    method displayHomeUp (line 232) | public void displayHomeUp(boolean display) {
    method onFitStatusUpdated (line 240) | public void onFitStatusUpdated(boolean connected) {
    method onBackPressed (line 271) | @Override
    method setActionBarTitle (line 287) | public void setActionBarTitle(int string_res_id) {
    method setActionBarTitle (line 291) | public void setActionBarTitle(String title) {
    method onStart (line 295) | @Override
    method onCreate (line 301) | @Override
    method onPause (line 332) | @Override
    method onResume (line 349) | @Override
    method onStop (line 361) | @Override
    method canPressBackButton (line 373) | private boolean canPressBackButton() {
    method checkDisplayEndScreen (line 377) | private void checkDisplayEndScreen() {
    method displayEndScreen (line 388) | private void displayEndScreen() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/MainService.java
  class MainService (line 58) | public class MainService extends Service implements Runnable, MediaPlaye...
    class AudioQueueItem (line 90) | private class AudioQueueItem{
      method AudioQueueItem (line 94) | AudioQueueItem(Uri uri, MediaPlayer.OnCompletionListener listener) {
      method equals (line 99) | @Override
    type State (line 109) | private enum State {
    method onReceive (line 121) | @Override
    method run (line 134) | @Override
    method userAuthenticated (line 143) | public void userAuthenticated() {
    method reconnectGoogleApi (line 147) | public void reconnectGoogleApi() {
    method ConnectGoogleFitApiClient (line 152) | public void ConnectGoogleFitApiClient(Activity activity) {
    method unlockAchievement (line 163) | public boolean unlockAchievement(String achievementId) {
    method loadAndStartMission (line 175) | public void loadAndStartMission(String missionFilePath, String mission...
    method startMission (line 216) | private void startMission() {
    method endMission (line 223) | public void endMission() {
    method onCreate (line 227) | @Override
    method onStartCommand (line 299) | @Override
    method onBind (line 306) | @Override
    method onUnbind (line 314) | @Override
    method onRebind (line 321) | @Override
    method onDestroy (line 332) | @Override
    method onCompletion (line 354) | @Override
    class MainBinder (line 363) | public class MainBinder extends Binder {
      method getService (line 364) | MainService getService() {
    method makeNotificationAction (line 376) | public NotificationCompat.Action makeNotificationAction(Intent intent,
    method postActionNotification (line 388) | public void postActionNotification(NotificationOptions options) {
    method isMissionRunning (line 406) | public boolean isMissionRunning() {
    method shouldDisplayEndScreen (line 410) | public boolean shouldDisplayEndScreen() {
    method reset (line 414) | public void reset() {
    method getOverallFictionalProgress (line 418) | public ArrayList<String> getOverallFictionalProgress() {
    method getFitnessStatistics (line 426) | public ArrayList<String> getFitnessStatistics() {
    method getCurrentMission (line 430) | public Mission getCurrentMission() {
    method queueSound (line 439) | public void queueSound(Uri uri, MediaPlayer.OnCompletionListener liste...
    method dequeueSound (line 447) | public void dequeueSound(Uri uri) {
    method obtainAudioFocus (line 456) | public boolean obtainAudioFocus() {
    method endPlayback (line 470) | public void endPlayback() {
    method getTextToSpeech (line 475) | protected TextToSpeech getTextToSpeech() { return mIsTextToSpeechReady...
    method canEnterState (line 482) | private boolean canEnterState(State state) {
    method setAndInitNextState (line 520) | private void setAndInitNextState(State state) {
    method update (line 544) | private void update() {
    method playFirstInQueue (line 565) | private void playFirstInQueue() {
    method isReady (line 590) | private boolean isReady() {
    method enableBackNavigation (line 597) | private void enableBackNavigation() {
    method requestReselection (line 604) | private void requestReselection() {
    method broadcastStart (line 612) | private void broadcastStart() {
    method broadcastEnd (line 619) | private void broadcastEnd() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/Mission.java
  class Mission (line 45) | public class Mission implements OnDataPointListener {
    method Mission (line 109) | public Mission(MissionData data) {
    method changeCurrentMoment (line 118) | public void changeCurrentMoment(String nextMomentId, long now) {
    method readMoments (line 134) | public void readMoments(InputStream missionStream) throws MissionParse...
    method start (line 139) | public void start() {
    method cleanup (line 150) | public void cleanup() {
    method addMoment (line 169) | public void addMoment(String id, Moment moment) {
    method setFirstMomentId (line 173) | public void setFirstMomentId(String firstMomentId) {
    method update (line 177) | public void update() {
    method postFitnessNotification (line 205) | public void postFitnessNotification(String title) {
    method setService (line 217) | public void setService(MainService service) {
    method getService (line 221) | public MainService getService() {
    method isDone (line 225) | public boolean isDone() {
    method prepare (line 229) | public void prepare(GoogleApiClientWrapper googleApiClientWrapper) {
    method isWeaponCharged (line 238) | public boolean isWeaponCharged() {
    method getMissionData (line 242) | public MissionData getMissionData() {
    method restartMoment (line 249) | public void restartMoment() {
    method applyOutcome (line 255) | public void applyOutcome(Outcome outcome) {
    method getOverallFictionalProgress (line 264) | public ArrayList<String> getOverallFictionalProgress() {
    method onDataPoint (line 268) | @Override
    method onStepTaken (line 287) | public void onStepTaken(int steps) {
    method getNumSteps (line 299) | public int getNumSteps() {
    method getMinutesPerMile (line 303) | public float getMinutesPerMile() {
    method getNumMinutesExercised (line 307) | public int getNumMinutesExercised() {
    method getNumSecondsExercised (line 314) | public int getNumSecondsExercised() {
    method getWeaponChargedPercentage (line 320) | public int getWeaponChargedPercentage() {
    method getChallengePace (line 334) | public float getChallengePace() {
    method onRebind (line 338) | public void onRebind() {
    method getMaximumMinutesPerMile (line 345) | public static float getMaximumMinutesPerMile() {
    method getFitnessStatistics (line 349) | public ArrayList<String> getFitnessStatistics() {
    method calculateAverageMinutesPerMile (line 363) | private void calculateAverageMinutesPerMile() {
    method updateChallengePace (line 377) | private void updateChallengePace(float averageMinutesPerMile) {
    method evaluateChallengePace (line 397) | private void evaluateChallengePace() {
    method getFitnessNotificationTitle (line 427) | private String getFitnessNotificationTitle() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/MissionData.java
  class MissionData (line 24) | public class MissionData {
    method MissionData (line 46) | public MissionData(String missionName, String missionId, float lengthO...
    method MissionData (line 65) | public MissionData(String missionName, String missionId, float lengthO...
    method getMissionName (line 71) | public String getMissionName() {
    method getMissionId (line 75) | public String getMissionId() {
    method getLengthOfMissionMinutes (line 79) | public float getLengthOfMissionMinutes() {
    method getLengthOfIntervalMinutes (line 83) | public float getLengthOfIntervalMinutes() {
    method getChallengePaceMinutesPerMile (line 87) | public float getChallengePaceMinutesPerMile() {
    method getMomentFromId (line 91) | public Moment getMomentFromId(String momentId) {
    method getCurrentMomentId (line 95) | public String getCurrentMomentId() {
    method getFirstMomentId (line 99) | public String getFirstMomentId() {
    method setCurrentMomentId (line 103) | public void setCurrentMomentId(String currentMomentId) {
    method addMoment (line 107) | public void addMoment(String momentId, Moment moment) {
    method setFirstMomentId (line 111) | public void setFirstMomentId(String firstMomentId) {
    method getCurrentMoment (line 115) | public Moment getCurrentMoment() {
    method getNumMoments (line 119) | public int getNumMoments() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/Moment.java
  class Moment (line 27) | public abstract class Moment {
    method Moment (line 40) | protected Moment(Mission mission) {
    method getMission (line 44) | public Mission getMission() {
    method isDone (line 52) | public boolean isDone() {
    method setIsDone (line 56) | public void setIsDone(boolean isDone) {
    method update (line 65) | public void update(long nowNanos) {
    method getNextMomentId (line 77) | public abstract String getNextMomentId();
    method start (line 83) | public void start(long nowNanos) {
    method end (line 91) | public abstract void end();
    method restart (line 96) | public abstract void restart(long nowNanos);
    method restartWithDelay (line 98) | public void restartWithDelay(long nowNanos, float secondsDelayRestart) {
    method getFictionalProgress (line 104) | public abstract ArrayList<String> getFictionalProgress();

FILE: app/src/main/java/com/google/fpl/gim/examplegame/MomentData.java
  class MomentData (line 24) | public abstract class MomentData {
    method MomentData (line 39) | public MomentData(String momentId, String nextMomentId, ArrayList<Stri...
    method getMomentId (line 45) | public String getMomentId() {
    method getNextMomentId (line 49) | public String getNextMomentId() {
    method getFictionalProgress (line 53) | public ArrayList<String> getFictionalProgress() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/Outcome.java
  class Outcome (line 22) | public class Outcome {
    method Outcome (line 26) | public Outcome(boolean depleteWeaponCharge, boolean incrementNumEnemie...
    method weaponChargeDepleted (line 31) | public boolean weaponChargeDepleted() {
    method numEnemiesDefeatedIncremented (line 35) | public boolean numEnemiesDefeatedIncremented() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/SfxMoment.java
  class SfxMoment (line 28) | public class SfxMoment extends Moment implements MediaPlayer.OnCompletio...
    method SfxMoment (line 32) | public SfxMoment(Mission mission, SfxMomentData data) {
    method start (line 37) | @Override
    method end (line 43) | @Override
    method getNextMomentId (line 47) | @Override
    method onCompletion (line 56) | @Override
    method restart (line 62) | @Override
    method getMomentData (line 69) | public SfxMomentData getMomentData() {
    method getFictionalProgress (line 73) | @Override

FILE: app/src/main/java/com/google/fpl/gim/examplegame/SfxMomentData.java
  class SfxMomentData (line 26) | public class SfxMomentData extends MomentData {
    method SfxMomentData (line 38) | public SfxMomentData(String momentId, String nextMomentId,
    method getUriAsset (line 44) | public Uri getUriAsset() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/SpokenTextMoment.java
  class SpokenTextMoment (line 31) | public class SpokenTextMoment extends Moment {
    method onStart (line 43) | @Override
    method onDone (line 47) | @Override
    method onError (line 56) | @Override
    method SpokenTextMoment (line 61) | public SpokenTextMoment(Mission mission, SpokenTextMomentData data) {
    method start (line 66) | @Override
    method end (line 79) | @Override
    method getNextMomentId (line 86) | public String getNextMomentId() {
    method speak (line 93) | private void speak() {
    method restart (line 102) | @Override
    method getMomentData (line 109) | public SpokenTextMomentData getMomentData() {
    method getFictionalProgress (line 113) | @Override

FILE: app/src/main/java/com/google/fpl/gim/examplegame/SpokenTextMomentData.java
  class SpokenTextMomentData (line 24) | public class SpokenTextMomentData extends MomentData {
    method SpokenTextMomentData (line 35) | public SpokenTextMomentData(String momentId, String nextMomentId,
    method getTextToSpeak (line 41) | public String getTextToSpeak() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/TimerMoment.java
  class TimerMoment (line 26) | public class TimerMoment extends Moment {
    method TimerMoment (line 32) | public TimerMoment(Mission mission, TimerMomentData data) {
    method update (line 37) | @Override
    method start (line 42) | @Override
    method end (line 49) | @Override
    method hasMomentTimeElapsed (line 60) | public boolean hasMomentTimeElapsed(long nowNanos) {
    method setStartTimeNanos (line 64) | public void setStartTimeNanos(long startTimeNanos) {
    method getNextMomentId (line 68) | @Override
    method restart (line 73) | @Override
    method getMomentData (line 79) | public TimerMomentData getMomentData() {
    method getFictionalProgress (line 83) | @Override

FILE: app/src/main/java/com/google/fpl/gim/examplegame/TimerMomentData.java
  class TimerMomentData (line 24) | public class TimerMomentData extends MomentData {
    method TimerMomentData (line 36) | public TimerMomentData(String momentId, String nextMomentId,
    method getLengthMinutes (line 42) | public float getLengthMinutes() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/google/FitDataTypeSetting.java
  class FitDataTypeSetting (line 24) | public class FitDataTypeSetting {
    method FitDataTypeSetting (line 30) | public FitDataTypeSetting(
    method isRequired (line 38) | public boolean isRequired() {
    method getDataType (line 42) | public DataType getDataType() {
    method getSamplingRateSeconds (line 46) | public long getSamplingRateSeconds() {
    method getAccuracyMode (line 50) | public int getAccuracyMode() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/google/FitResultCallback.java
  class FitResultCallback (line 31) | public class FitResultCallback <R extends Result>
    type RegisterType (line 36) | public enum RegisterType {
    method FitResultCallback (line 54) | public FitResultCallback(
    method onResult (line 63) | @Override
    method onSensorResult (line 80) | private void onSensorResult(Status status) {
    method onRecordingResult (line 94) | private void onRecordingResult(Status status) {
    method onRecordingSubscription (line 102) | private void onRecordingSubscription(Status status) {
    method onRecordingUnsubscription (line 117) | private void onRecordingUnsubscription(Status status) {
    method onSessionResult (line 125) | private void onSessionResult(Status status) {
    method onSessionSubscription (line 133) | private void onSessionSubscription(Status status) {
    method onSessionUnsubscription (line 143) | private void onSessionUnsubscription(Status status) {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/google/GoogleApiClientWrapper.java
  class GoogleApiClientWrapper (line 54) | public class GoogleApiClientWrapper implements ConnectionCallbacks, OnCo...
    method buildGoogleApiClient (line 70) | public void buildGoogleApiClient(Activity activity) {
    method connect (line 88) | public void connect() {
    method disconnect (line 95) | public void disconnect() {
    method onConnected (line 101) | @Override
    method onConnectionSuspended (line 109) | @Override
    method onConnectionFailed (line 124) | @Override
    method startFitDataSession (line 154) | public void startFitDataSession(FitDataTypeSetting[] dataTypeSettings,
    method endFitDataSession (line 181) | public void endFitDataSession(
    method isSignedIn (line 197) | public boolean isSignedIn() {
    method userAuthenticated (line 201) | public void userAuthenticated() {
    method isClientReady (line 205) | public boolean isClientReady() {
    method getGoogleApiClient (line 218) | public GoogleApiClient getGoogleApiClient() {
    method sensorRegistered (line 222) | protected void sensorRegistered(DataType dataType) {
    method startRecordingFitData (line 237) | private void startRecordingFitData(FitDataTypeSetting dataTypeSetting) {
    method stopRecordingFitData (line 244) | private void stopRecordingFitData(FitDataTypeSetting dataTypeSetting) {
    method registerFitDataListener (line 257) | private void registerFitDataListener(
    method unregisterFitDataListener (line 273) | private void unregisterFitDataListener(OnDataPointListener listener) {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/EndSummaryFragment.java
  class EndSummaryFragment (line 35) | public class EndSummaryFragment extends Fragment {
    method onCreateView (line 40) | @Override
    method onResume (line 49) | @Override public void onResume() {
    method displayStats (line 58) | public void displayStats(ArrayList<String> fictionalProgress,
    method onFitStatusUpdated (line 66) | public void onFitStatusUpdated(boolean connected) {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/FitnessDataDisplayFragment.java
  class FitnessDataDisplayFragment (line 34) | public class FitnessDataDisplayFragment extends Fragment {
    method onCreateView (line 43) | @Override
    method onResume (line 59) | @Override public void onResume() {
    method setNumSteps (line 64) | public void setNumSteps(int numSteps) {
    method setMinutesPerMile (line 68) | public void setMinutesPerMile(float minutesPerMile, float challengePac...
    method setTimeExercised (line 83) | public void setTimeExercised(int numMinutes, int numSeconds) {
    method setWeaponChargedPercentage (line 87) | public void setWeaponChargedPercentage(int weaponChargedPercentage) {
    method setFitnessStats (line 92) | public void setFitnessStats(Mission mission) {
    method setMissionName (line 99) | public void setMissionName(String missionName) {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/GameViews.java
  class GameViews (line 33) | public class GameViews {
    method GameViews (line 57) | public GameViews() {
    method initializeFragments (line 64) | public void initializeFragments(Activity activity) {
    method restoreFragments (line 92) | public void restoreFragments(Activity activity) {
    method getStartMenuFragment (line 147) | public StartMenuFragment getStartMenuFragment() {
    method getListOfMissionsFragment (line 151) | public MissionSelectionFragment getListOfMissionsFragment() {
    method getRunSpecificationsFragment (line 155) | public RunSpecificationSelectionFragment getRunSpecificationsFragment() {
    method getMusicSelectionFragment (line 159) | public MusicSelectionFragment getMusicSelectionFragment() {
    method getEndSummaryFragment (line 163) | public EndSummaryFragment getEndSummaryFragment() {
    method getFitnessDataDisplayFragment (line 167) | public FitnessDataDisplayFragment getFitnessDataDisplayFragment() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/MissionSelectionFragment.java
  class MissionSelectionFragment (line 44) | public class MissionSelectionFragment extends ListFragment {
    method onCreate (line 56) | @Override
    method onCreateView (line 110) | @Override
    method onViewCreated (line 116) | @Override
    method onListItemClick (line 122) | @Override
    method onResume (line 127) | @Override public void onResume() {
    method onMissionSelected (line 132) | public void onMissionSelected(int position) {
    method getSelectedMissionName (line 153) | public String getSelectedMissionName() {
    method getSelectedAssetPath (line 157) | public String getSelectedAssetPath() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/MusicSelectionFragment.java
  class MusicSelectionFragment (line 32) | public class MusicSelectionFragment extends Fragment {
    method onCreateView (line 37) | @Override
    method onResume (line 47) | @Override public void onResume() {
    method disableReadyButton (line 52) | public void disableReadyButton() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/NotificationOptions.java
  class NotificationOptions (line 27) | public class NotificationOptions {
    method NotificationOptions (line 49) | public NotificationOptions() {
    method NotificationOptions (line 55) | public NotificationOptions(int notificationId, int smallIconResourceId...
    method getDefaultNotificationOptions (line 74) | public static NotificationOptions getDefaultNotificationOptions() {
    method setRequiredOptions (line 92) | public void setRequiredOptions(int smallIconResourceId, String title,
    method setActions (line 104) | public void setActions(NotificationCompat.Action[] actions) {
    method setNotificationDefaults (line 113) | public void setNotificationDefaults(int notificationDefaults) {
    method setNotificationPriority (line 121) | public void setNotificationPriority(int notificationPriority) {
    method setNotificationId (line 125) | public void setNotificationId(int id) {
    method setPriorityAsDefault (line 129) | public void setPriorityAsDefault() {
    method setPriorityAsHigh (line 133) | public void setPriorityAsHigh() {
    method setPriorityAsMax (line 137) | public void setPriorityAsMax() {
    method setContent (line 141) | public void setContent(String content) {
    method setTitle (line 145) | public void setTitle(String title) {
    method setVibratePattern (line 149) | public void setVibratePattern(long[] vibratePattern) { this.mVibratePa...
    method getNotificationPriority (line 151) | public int getNotificationPriority() {
    method getNotificationId (line 155) | public int getNotificationId() {
    method getSmallIconResourceId (line 159) | public int getSmallIconResourceId() {
    method getTitle (line 163) | public String getTitle() {
    method getContent (line 167) | public String getContent() {
    method getActions (line 171) | public NotificationCompat.Action[] getActions() {
    method getNotificationDefaults (line 175) | public int getNotificationDefaults() {
    method getVibratePattern (line 179) | public long[] getVibratePattern() { return mVibratePattern; }

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/RunSpecificationSelectionFragment.java
  class RunSpecificationSelectionFragment (line 33) | public class RunSpecificationSelectionFragment extends Fragment {
    method onCreateView (line 71) | @Override
    method onResume (line 100) | @Override public void onResume() {
    method onEnterPressed (line 105) | public void onEnterPressed() {
    method updateChallengePaceText (line 130) | private void updateChallengePaceText(int progress) {
    method calculateChallengePaceFromProgress (line 142) | private float calculateChallengePaceFromProgress(int progress) {
    method getSelectedMissionLengthMinutes (line 148) | public float getSelectedMissionLengthMinutes() {
    method getSelectedIntervalLengthMinutes (line 152) | public float getSelectedIntervalLengthMinutes() {
    method getSelectedChallengePaceMinutesPerMile (line 156) | public float getSelectedChallengePaceMinutesPerMile() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/gui/StartMenuFragment.java
  class StartMenuFragment (line 33) | public class StartMenuFragment extends Fragment {
    method onCreateView (line 39) | @Override
    method onResume (line 48) | @Override public void onResume() {
    method onStartButtonPressed (line 53) | public void onStartButtonPressed() {
    method onFitStatusUpdated (line 67) | public void onFitStatusUpdated(boolean connected) {
    method updateStartButton (line 72) | private void updateStartButton() {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/utils/MissionParseException.java
  class MissionParseException (line 22) | public class MissionParseException extends Exception {
    method MissionParseException (line 25) | public MissionParseException() {
    method MissionParseException (line 29) | public MissionParseException(String s) {

FILE: app/src/main/java/com/google/fpl/gim/examplegame/utils/MissionParser.java
  class MissionParser (line 51) | public class MissionParser {
    method parseMission (line 105) | public static void parseMission(InputStream missionStream, Mission mis...
    method isElementNode (line 173) | private static boolean isElementNode(Node node) {
    method parseSfxMoment (line 183) | private static SfxMomentData parseSfxMoment(String momentId, Element m...
    method parseTimerMoment (line 199) | private static TimerMomentData parseTimerMoment(String momentId, Eleme...
    method parseSpokenTextMoment (line 217) | private static SpokenTextMomentData parseSpokenTextMoment(String momen...
    method parseChoiceMomentElement (line 235) | private static ChoiceMomentData parseChoiceMomentElement(String momentId,
    method parseChoiceElement (line 283) | private static Choice parseChoiceElement(Element choiceElement) throws...
    method parseOutcomeElement (line 311) | private static Outcome parseOutcomeElement(Element outcomeElement) {
    method getNextMomentId (line 324) | private static String getNextMomentId(Element element) throws MissionP...
    method getDescription (line 338) | private static String getDescription(Element element) throws MissionPa...
    method findSingleChildElementByTag (line 348) | private static Element findSingleChildElementByTag(Element parent, Str...
    method parseNextMomentElement (line 367) | private static String parseNextMomentElement(Element nextMomentElement) {
    method parseLengthMinutesElement (line 375) | private static float parseLengthMinutesElement(Element lengthMinutesEl...
    method parseUriElement (line 384) | private static Uri parseUriElement(Element uriElement) throws MissionP...
    method parseTextToSpeakElement (line 392) | private static String parseTextToSpeakElement(Element textToSpeakElement)
    method parseDescriptionElement (line 401) | private static String parseDescriptionElement(Element descriptionElement)
    method parseTimeoutLengthMinutesElement (line 410) | private static float parseTimeoutLengthMinutesElement(Element timeoutL...
    method parseDefaultChoiceElement (line 419) | private static String parseDefaultChoiceElement(Element defaultChoiceE...
    method getMissionName (line 437) | public static String getMissionName(InputStream missionStream) throws ...
    method getDocumentFromInputStream (line 467) | private static Document getDocumentFromInputStream(InputStream mission...
    method parseMomentFictionalProgress (line 498) | private static ArrayList<String> parseMomentFictionalProgress(Element ...
    method parseFictionalProgressElement (line 514) | private static String parseFictionalProgressElement(Element element)
    method parseNestedFictionalProgress (line 527) | private static ArrayList<String> parseNestedFictionalProgress(Element ...
    method parseIconElement (line 541) | private static String parseIconElement(Element iconElement) throws Mis...

FILE: app/src/main/java/com/google/fpl/gim/examplegame/utils/Utils.java
  class Utils (line 29) | public class Utils {
    method nanosToSeconds (line 41) | public static float nanosToSeconds(long nanos) {
    method secondsToNanos (line 45) | public static long secondsToNanos(float seconds) {
    method minutesToNanos (line 49) | public static long minutesToNanos(float min) {
    method nanosToMinutes (line 53) | public static float nanosToMinutes(long nanos) {
    method feetToMiles (line 57) | public static float feetToMiles(float feet) {
    method secondsToMinutes (line 61) | public static float secondsToMinutes(float seconds) {
    method metersPerSecondToMinutesPerMile (line 65) | public static float metersPerSecondToMinutesPerMile(float metersPerSec...
    method logDebug (line 80) | public static void logDebug(String tag, String message) {
    method makeFilePath (line 92) | public static String makeFilePath(String rootDirectory, ArrayList<Stri...

FILE: docs/generate_docs.py
  function main (line 29) | def main():
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (515K chars).
[
  {
    "path": ".gitignore",
    "chars": 365,
    "preview": "#built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ng"
  },
  {
    "path": ".gitmodules",
    "chars": 108,
    "preview": "[submodule \"dependencies/fplutil\"]\n\tpath = dependencies/fplutil\n\turl = http://github.com/google/fplutil.git\n"
  },
  {
    "path": ".idea/.name",
    "chars": 11,
    "preview": "ExampleGame"
  },
  {
    "path": ".idea/compiler.xml",
    "chars": 711,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <option name"
  },
  {
    "path": ".idea/copyright/Apache_2_0.xml",
    "chars": 945,
    "preview": "<component name=\"CopyrightManager\">\n  <copyright>\n    <option name=\"notice\" value=\"Copyright 2015 Google Inc. All Rights"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 84,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"Apache 2.0\" />\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 166,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\" useUTFGuessing=\"true\" native2A"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 640,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linke"
  },
  {
    "path": ".idea/libraries/appcompat_v7_21_0_3.xml",
    "chars": 412,
    "preview": "<component name=\"libraryTable\">\n  <library name=\"appcompat-v7-21.0.3\">\n    <CLASSES>\n      <root url=\"file://$PROJECT_DI"
  },
  {
    "path": ".idea/libraries/junit_3_8.xml",
    "chars": 294,
    "preview": "<component name=\"libraryTable\">\n  <library name=\"junit-3.8\">\n    <CLASSES>\n      <root url=\"jar://$USER_HOME$/.gradle/ca"
  },
  {
    "path": ".idea/libraries/play_services_6_1_71.xml",
    "chars": 421,
    "preview": "<component name=\"libraryTable\">\n  <library name=\"play-services-6.1.71\">\n    <CLASSES>\n      <root url=\"file://$PROJECT_D"
  },
  {
    "path": ".idea/libraries/support_annotations_21_0_3.xml",
    "chars": 521,
    "preview": "<component name=\"libraryTable\">\n  <library name=\"support-annotations-21.0.3\">\n    <CLASSES>\n      <root url=\"jar://$USER"
  },
  {
    "path": ".idea/libraries/support_v4_21_0_3.xml",
    "chars": 725,
    "preview": "<component name=\"libraryTable\">\n  <library name=\"support-v4-21.0.3\">\n    <CLASSES>\n      <root url=\"file://$PROJECT_DIR$"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 396,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EntryPointsManager\">\n    <entry_points v"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 361,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/scopes/scope_settings.xml",
    "chars": 139,
    "preview": "<component name=\"DependencyValidationManager\">\n  <state>\n    <option name=\"SKIP_IMPORT_STATEMENTS\" value=\"false\" />\n  </"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 166,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "CONTRIBUTING",
    "chars": 1433,
    "preview": "Contributing    {#contributing}\n============\n\nWant to contribute? Great! First, read this page (including the small prin"
  },
  {
    "path": "ExampleGame.iml",
    "chars": 828,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module external.linked.project.path=\"$MODULE_DIR$\" external.root.project.path=\"$"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "app/app.iml",
    "chars": 7332,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module external.linked.project.path=\"$MODULE_DIR$\" external.root.project.path=\"$"
  },
  {
    "path": "app/build.gradle",
    "chars": 1191,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/androidTest/java/com/google/fpl/gim/examplegame/MissionParseTest.java",
    "chars": 55761,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 2194,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/choice_mission.xml",
    "chars": 2095,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/mission.xml",
    "chars": 5174,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/sfx mission.xml",
    "chars": 1311,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/spoken_plus_timer_mission.xml",
    "chars": 1452,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/texttospeechmission.xml",
    "chars": 1123,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/legacy_missions/timermission.xml",
    "chars": 1219,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/01_sample_mission_1.xml",
    "chars": 5548,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/02_sample_mission_2.xml",
    "chars": 16214,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_01_timer_moment.xml",
    "chars": 1054,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_02_spoken_text_moment.xml",
    "chars": 1077,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_03_choice_moment.xml",
    "chars": 2036,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_04_sfx_moment.xml",
    "chars": 1130,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_05_broken_timer_moment.xml",
    "chars": 1084,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/assets/missions/ex_06_long_timer_mission.xml",
    "chars": 932,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/Choice.java",
    "chars": 2562,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMoment.java",
    "chars": 7667,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/ChoiceMomentData.java",
    "chars": 3507,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/MainActivity.java",
    "chars": 16991,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/MainService.java",
    "chars": 21661,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/Mission.java",
    "chars": 16887,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/MissionData.java",
    "chars": 4281,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/Moment.java",
    "chars": 3184,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/MomentData.java",
    "chars": 1811,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/Outcome.java",
    "chars": 1298,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/SfxMoment.java",
    "chars": 2101,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/SfxMomentData.java",
    "chars": 1607,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/SpokenTextMoment.java",
    "chars": 3819,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/SpokenTextMomentData.java",
    "chars": 1607,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/TimerMoment.java",
    "chars": 2553,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/TimerMomentData.java",
    "chars": 1572,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/google/FitDataTypeSetting.java",
    "chars": 1605,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/google/FitResultCallback.java",
    "chars": 5397,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/google/GoogleApiClientWrapper.java",
    "chars": 11974,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/EndSummaryFragment.java",
    "chars": 2544,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/FitnessDataDisplayFragment.java",
    "chars": 3892,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/GameViews.java",
    "chars": 7121,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/MissionSelectionFragment.java",
    "chars": 5917,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/MusicSelectionFragment.java",
    "chars": 1959,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/NotificationOptions.java",
    "chars": 6030,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/RunSpecificationSelectionFragment.java",
    "chars": 6525,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/gui/StartMenuFragment.java",
    "chars": 2903,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/utils/MissionParseException.java",
    "chars": 1031,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/utils/MissionParser.java",
    "chars": 23978,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/java/com/google/fpl/gim/examplegame/utils/Utils.java",
    "chars": 3304,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "app/src/main/res/anim/slide_in_right.xml",
    "chars": 929,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/anim/slide_out_left.xml",
    "chars": 930,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/drawable/weapon_charge_progress.xml",
    "chars": 1357,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 1007,
    "preview": "<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "app/src/main/res/layout/end_screen.xml",
    "chars": 2128,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/menu_list_item.xml",
    "chars": 1505,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/layout/menu_mission_list.xml",
    "chars": 2830,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/layout/menu_music_selection.xml",
    "chars": 1539,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/menu_run_specifications.xml",
    "chars": 2407,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/menu_start.xml",
    "chars": 2493,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/placeholder_fragment.xml",
    "chars": 1121,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/layout/step_display.xml",
    "chars": 3056,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under "
  },
  {
    "path": "app/src/main/res/menu/main.xml",
    "chars": 880,
    "preview": "<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 863,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 1708,
    "preview": "<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "chars": 907,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 5044,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under t"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 4612,
    "preview": "<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "chars": 992,
    "preview": "<!--\n  ~ Copyright 2015 Google Inc. All Rights Reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "build.gradle",
    "chars": 929,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  },
  {
    "path": "docs/generate_docs.py",
    "chars": 1435,
    "preview": "#!/usr/bin/python\n# Copyright 2015 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 ("
  },
  {
    "path": "docs/src/contributing.md",
    "chars": 1433,
    "preview": "Contributing    {#contributing}\n============\n\nWant to contribute? Great! First, read this page (including the small prin"
  },
  {
    "path": "docs/src/doxyfile",
    "chars": 101193,
    "preview": "# Doxyfile 1.8.5\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) "
  },
  {
    "path": "docs/src/doxygen_layout.xml",
    "chars": 6748,
    "preview": "<!-- Copyright 2015 Google Inc. All rights reserved.\n\n     Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "docs/src/index.md",
    "chars": 3133,
    "preview": "Games in Motion\n===============\n\nGames in Motion    {#games_in_motion_index}\n===============\n\n[Games in Motion][] is a g"
  },
  {
    "path": "docs/src/programmers_guide/assets.md",
    "chars": 2755,
    "preview": "Modifying Assets {#games_in_motion_guide_assets}\n================\n\n### Overview\n\nThe game utilizes a data-driven compone"
  },
  {
    "path": "docs/src/programmers_guide/audio.md",
    "chars": 3290,
    "preview": "Audio {#games_in_motion_guide_audio}\n=====\n\nAudio is essential to [Games in Motion][]. It signals game progression to th"
  },
  {
    "path": "docs/src/programmers_guide/building.md",
    "chars": 2351,
    "preview": "Building    {#games_in_motion_guide_building}\n========\n\nDevelopers can build the game from source for [Android][], using"
  },
  {
    "path": "docs/src/programmers_guide/core.md",
    "chars": 4629,
    "preview": "Games in Motion Core    {#games_in_motion_guide_core}\n====================\n\nThere are two main components of the game, `"
  },
  {
    "path": "docs/src/programmers_guide/gameplay.md",
    "chars": 1165,
    "preview": "Gameplay {#games_in_motion_guide_gameplay}\n===============\n\nThe player selects a mission to play and exercises (walk or "
  },
  {
    "path": "docs/src/programmers_guide/google_api.md",
    "chars": 1986,
    "preview": "Google API {#games_in_motion_guide_google_api}\n==========\n\nGoogle APIs provide feature access that is important to Games"
  },
  {
    "path": "docs/src/programmers_guide/mission.md",
    "chars": 2477,
    "preview": "Mission System {#games_in_motion_guide_mission}\n==============\n\nThe data-driven mission system is controlled by the `Mis"
  },
  {
    "path": "docs/src/programmers_guide/overview.md",
    "chars": 1919,
    "preview": "Overview    {#games_in_motion_guide_overview}\n========\n\n## Downloading\n\n[Games in Motion][] can be downloaded from [GitH"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 833,
    "preview": "#\n# Copyright 2015 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n"
  },
  {
    "path": "gradle.properties",
    "chars": 1454,
    "preview": "#\n# Copyright 2015 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n"
  },
  {
    "path": "gradlew",
    "chars": 5080,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "prototype.iml",
    "chars": 600,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module external.linked.project.path=\"$MODULE_DIR$\" external.root.project.path=\"$"
  },
  {
    "path": "readme.md",
    "chars": 2411,
    "preview": "Games in Motion   {#games_in_motion_readme}\n===============\n\nGames in Motion Version 1.0.0\n\n[Games in Motion][] is a sim"
  },
  {
    "path": "settings.gradle",
    "chars": 632,
    "preview": "/*\n * Copyright 2015 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the googlesamples/android-play-games-in-motion GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (479.0 KB), approximately 111.6k tokens, and a symbol index with 406 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!