From 2ecd44724d92588464119c28def0121be886f35a Mon Sep 17 00:00:00 2001 From: Breana Tate Date: Fri, 13 Dec 2024 16:43:04 -0500 Subject: [PATCH] Removes deprecated samples from main --- .github/workflows/ExerciseSample.yml | 50 --- .github/workflows/MeasureData.yml | 50 --- .github/workflows/PassiveData.yml | 50 --- .github/workflows/PassiveGoals.yml | 50 --- health-services/ExerciseSample/README.md | 36 -- .../ExerciseSample/app/build.gradle | 89 ----- .../ExerciseSample/app/proguard-rules.pro | 22 -- .../app/src/main/AndroidManifest.xml | 74 ---- .../com/example/exercise/ExerciseFragment.kt | 356 ----------------- .../com/example/exercise/ExerciseService.kt | 368 ------------------ .../exercise/ExerciseServiceConnection.kt | 61 --- .../com/example/exercise/FormattingUtils.kt | 70 ---- .../example/exercise/HealthServicesManager.kt | 239 ------------ .../java/com/example/exercise/MainActivity.kt | 76 ---- .../com/example/exercise/MainApplication.kt | 28 -- .../java/com/example/exercise/MainModule.kt | 47 --- .../com/example/exercise/MainViewModel.kt | 57 --- .../com/example/exercise/PrepareFragment.kt | 124 ------ .../com/example/exercise/StartupFragment.kt | 59 --- .../main/res/drawable/ic_calories_burned.xml | 26 -- .../app/src/main/res/drawable/ic_cancel.xml | 27 -- .../app/src/main/res/drawable/ic_check.xml | 26 -- .../app/src/main/res/drawable/ic_clock.xml | 29 -- .../app/src/main/res/drawable/ic_distance.xml | 26 -- .../main/res/drawable/ic_heart_outline.xml | 26 -- .../app/src/main/res/drawable/ic_lap.xml | 30 -- .../res/drawable/ic_launcher_background.xml | 186 --------- .../res/drawable/ic_launcher_foreground.xml | 47 --- .../main/res/drawable/ic_not_available.xml | 27 -- .../app/src/main/res/drawable/ic_run.xml | 26 -- .../app/src/main/res/layout/activity_main.xml | 34 -- .../src/main/res/layout/fragment_exercise.xml | 197 ---------- .../res/layout/fragment_not_available.xml | 55 --- .../src/main/res/layout/fragment_prepare.xml | 66 ---- .../src/main/res/layout/fragment_startup.xml | 53 --- .../app/src/main/res/mipmap/ic_launcher.xml | 21 - .../src/main/res/mipmap/ic_launcher_round.xml | 21 - .../app/src/main/res/navigation/nav_graph.xml | 59 --- .../app/src/main/res/values-round/dimens.xml | 7 - .../app/src/main/res/values/dimens.xml | 26 -- .../app/src/main/res/values/strings.xml | 36 -- .../app/src/main/res/values/themes.xml | 25 -- health-services/ExerciseSample/build.gradle | 49 --- .../ExerciseSample/gradle.properties | 36 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - health-services/ExerciseSample/gradlew | 252 ------------ health-services/ExerciseSample/gradlew.bat | 94 ----- .../screenshots/exercise_available.png | Bin 50907 -> 0 bytes .../screenshots/exercise_in_progress.png | Bin 53014 -> 0 bytes .../screenshots/exercise_not_available.png | Bin 50914 -> 0 bytes .../screenshots/ongoing_notification.png | Bin 64157 -> 0 bytes .../ExerciseSample/settings.gradle | 18 - health-services/MeasureData/README.md | 50 --- health-services/MeasureData/app/build.gradle | 82 ---- .../MeasureData/app/proguard-rules.pro | 22 -- .../app/src/main/AndroidManifest.xml | 61 --- .../measuredata/HealthServicesManager.kt | 82 ---- .../com/example/measuredata/MainActivity.kt | 108 ----- .../example/measuredata/MainApplication.kt | 28 -- .../com/example/measuredata/MainModule.kt | 39 -- .../com/example/measuredata/MainViewModel.kt | 82 ---- .../src/main/res/drawable/ic_broken_heart.xml | 42 -- .../app/src/main/res/drawable/ic_heart.xml | 27 -- .../res/drawable/ic_launcher_background.xml | 186 --------- .../res/drawable/ic_launcher_foreground.xml | 47 --- .../app/src/main/res/layout/activity_main.xml | 131 ------- .../app/src/main/res/mipmap/ic_launcher.xml | 21 - .../src/main/res/mipmap/ic_launcher_round.xml | 21 - .../app/src/main/res/values/dimens.xml | 20 - .../app/src/main/res/values/strings.xml | 23 -- .../app/src/main/res/values/themes.xml | 23 -- health-services/MeasureData/build.gradle | 47 --- health-services/MeasureData/gradle.properties | 37 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - health-services/MeasureData/gradlew | 252 ------------ health-services/MeasureData/gradlew.bat | 94 ----- .../whs_measure_data_available.png | Bin 53471 -> 0 bytes .../whs_measure_data_not_available.png | Bin 51182 -> 0 bytes health-services/MeasureData/settings.gradle | 18 - health-services/PassiveData/README.md | 55 --- health-services/PassiveData/app/build.gradle | 87 ----- .../PassiveData/app/proguard-rules.pro | 22 -- .../app/src/main/AndroidManifest.xml | 92 ----- .../passivedata/HealthServicesManager.kt | 57 --- .../com/example/passivedata/MainActivity.kt | 107 ----- .../example/passivedata/MainApplication.kt | 46 --- .../com/example/passivedata/MainModule.kt | 43 -- .../com/example/passivedata/MainViewModel.kt | 79 ---- .../passivedata/PassiveDataRepository.kt | 86 ---- .../example/passivedata/PassiveDataService.kt | 22 -- .../example/passivedata/StartupReceiver.kt | 89 ----- .../src/main/res/drawable/ic_broken_heart.xml | 42 -- .../app/src/main/res/drawable/ic_heart.xml | 27 -- .../res/drawable/ic_launcher_background.xml | 186 --------- .../res/drawable/ic_launcher_foreground.xml | 47 --- .../app/src/main/res/layout/activity_main.xml | 128 ------ .../app/src/main/res/mipmap/ic_launcher.xml | 21 - .../src/main/res/mipmap/ic_launcher_round.xml | 21 - .../app/src/main/res/values/dimens.xml | 20 - .../app/src/main/res/values/strings.xml | 23 -- .../app/src/main/res/values/themes.xml | 23 -- health-services/PassiveData/build.gradle | 47 --- health-services/PassiveData/gradle.properties | 37 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - health-services/PassiveData/gradlew | 252 ------------ health-services/PassiveData/gradlew.bat | 94 ----- .../whs_passive_data_available.png | Bin 53139 -> 0 bytes .../whs_passive_data_not_available.png | Bin 50020 -> 0 bytes health-services/PassiveData/settings.gradle | 18 - health-services/PassiveGoals/README.md | 52 --- health-services/PassiveGoals/app/build.gradle | 87 ----- .../PassiveGoals/app/proguard-rules.pro | 22 -- .../app/src/main/AndroidManifest.xml | 89 ----- .../passivegoals/HealthServicesManager.kt | 91 ----- .../com/example/passivegoals/MainActivity.kt | 166 -------- .../example/passivegoals/MainApplication.kt | 46 --- .../com/example/passivegoals/MainModule.kt | 43 -- .../com/example/passivegoals/MainViewModel.kt | 78 ---- .../passivegoals/PassiveGoalService.kt | 31 -- .../passivegoals/PassiveGoalsRepository.kt | 83 ---- .../example/passivegoals/StartupReceiver.kt | 91 ----- .../src/main/res/drawable/ic_daily_goal.xml | 40 -- .../app/src/main/res/drawable/ic_floors.xml | 28 -- .../res/drawable/ic_launcher_background.xml | 186 --------- .../res/drawable/ic_launcher_foreground.xml | 47 --- .../main/res/drawable/ic_not_available.xml | 27 -- .../app/src/main/res/layout/activity_main.xml | 151 ------- .../app/src/main/res/mipmap/ic_launcher.xml | 21 - .../src/main/res/mipmap/ic_launcher_round.xml | 21 - .../app/src/main/res/values/dimens.xml | 20 - .../app/src/main/res/values/strings.xml | 26 -- .../app/src/main/res/values/themes.xml | 25 -- health-services/PassiveGoals/build.gradle | 48 --- .../PassiveGoals/gradle.properties | 37 -- .../gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - health-services/PassiveGoals/gradlew | 252 ------------ health-services/PassiveGoals/gradlew.bat | 94 ----- .../screenshots/passive_goals.png | Bin 57487 -> 0 bytes .../passive_goals_not_available.png | Bin 52982 -> 0 bytes health-services/PassiveGoals/settings.gradle | 24 -- renovate.json | 2 +- 145 files changed, 1 insertion(+), 8700 deletions(-) delete mode 100644 .github/workflows/ExerciseSample.yml delete mode 100644 .github/workflows/MeasureData.yml delete mode 100644 .github/workflows/PassiveData.yml delete mode 100644 .github/workflows/PassiveGoals.yml delete mode 100644 health-services/ExerciseSample/README.md delete mode 100644 health-services/ExerciseSample/app/build.gradle delete mode 100644 health-services/ExerciseSample/app/proguard-rules.pro delete mode 100644 health-services/ExerciseSample/app/src/main/AndroidManifest.xml delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseFragment.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseService.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseServiceConnection.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/FormattingUtils.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/HealthServicesManager.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainActivity.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainApplication.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainModule.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainViewModel.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/PrepareFragment.kt delete mode 100644 health-services/ExerciseSample/app/src/main/java/com/example/exercise/StartupFragment.kt delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_calories_burned.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_cancel.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_check.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_clock.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_distance.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_heart_outline.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_lap.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_not_available.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/drawable/ic_run.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/layout/activity_main.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/layout/fragment_exercise.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/layout/fragment_not_available.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/layout/fragment_prepare.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/layout/fragment_startup.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/mipmap/ic_launcher.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/mipmap/ic_launcher_round.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/navigation/nav_graph.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/values-round/dimens.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/values/dimens.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/values/strings.xml delete mode 100644 health-services/ExerciseSample/app/src/main/res/values/themes.xml delete mode 100644 health-services/ExerciseSample/build.gradle delete mode 100644 health-services/ExerciseSample/gradle.properties delete mode 100644 health-services/ExerciseSample/gradle/wrapper/gradle-wrapper.jar delete mode 100644 health-services/ExerciseSample/gradle/wrapper/gradle-wrapper.properties delete mode 100755 health-services/ExerciseSample/gradlew delete mode 100644 health-services/ExerciseSample/gradlew.bat delete mode 100644 health-services/ExerciseSample/screenshots/exercise_available.png delete mode 100644 health-services/ExerciseSample/screenshots/exercise_in_progress.png delete mode 100644 health-services/ExerciseSample/screenshots/exercise_not_available.png delete mode 100644 health-services/ExerciseSample/screenshots/ongoing_notification.png delete mode 100644 health-services/ExerciseSample/settings.gradle delete mode 100644 health-services/MeasureData/README.md delete mode 100644 health-services/MeasureData/app/build.gradle delete mode 100644 health-services/MeasureData/app/proguard-rules.pro delete mode 100644 health-services/MeasureData/app/src/main/AndroidManifest.xml delete mode 100644 health-services/MeasureData/app/src/main/java/com/example/measuredata/HealthServicesManager.kt delete mode 100644 health-services/MeasureData/app/src/main/java/com/example/measuredata/MainActivity.kt delete mode 100644 health-services/MeasureData/app/src/main/java/com/example/measuredata/MainApplication.kt delete mode 100644 health-services/MeasureData/app/src/main/java/com/example/measuredata/MainModule.kt delete mode 100644 health-services/MeasureData/app/src/main/java/com/example/measuredata/MainViewModel.kt delete mode 100644 health-services/MeasureData/app/src/main/res/drawable/ic_broken_heart.xml delete mode 100644 health-services/MeasureData/app/src/main/res/drawable/ic_heart.xml delete mode 100644 health-services/MeasureData/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 health-services/MeasureData/app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 health-services/MeasureData/app/src/main/res/layout/activity_main.xml delete mode 100644 health-services/MeasureData/app/src/main/res/mipmap/ic_launcher.xml delete mode 100644 health-services/MeasureData/app/src/main/res/mipmap/ic_launcher_round.xml delete mode 100644 health-services/MeasureData/app/src/main/res/values/dimens.xml delete mode 100644 health-services/MeasureData/app/src/main/res/values/strings.xml delete mode 100644 health-services/MeasureData/app/src/main/res/values/themes.xml delete mode 100644 health-services/MeasureData/build.gradle delete mode 100644 health-services/MeasureData/gradle.properties delete mode 100644 health-services/MeasureData/gradle/wrapper/gradle-wrapper.jar delete mode 100644 health-services/MeasureData/gradle/wrapper/gradle-wrapper.properties delete mode 100755 health-services/MeasureData/gradlew delete mode 100644 health-services/MeasureData/gradlew.bat delete mode 100644 health-services/MeasureData/screenshots/whs_measure_data_available.png delete mode 100644 health-services/MeasureData/screenshots/whs_measure_data_not_available.png delete mode 100644 health-services/MeasureData/settings.gradle delete mode 100644 health-services/PassiveData/README.md delete mode 100644 health-services/PassiveData/app/build.gradle delete mode 100644 health-services/PassiveData/app/proguard-rules.pro delete mode 100644 health-services/PassiveData/app/src/main/AndroidManifest.xml delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/HealthServicesManager.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/MainActivity.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/MainApplication.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/MainModule.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/MainViewModel.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/PassiveDataRepository.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/PassiveDataService.kt delete mode 100644 health-services/PassiveData/app/src/main/java/com/example/passivedata/StartupReceiver.kt delete mode 100644 health-services/PassiveData/app/src/main/res/drawable/ic_broken_heart.xml delete mode 100644 health-services/PassiveData/app/src/main/res/drawable/ic_heart.xml delete mode 100644 health-services/PassiveData/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 health-services/PassiveData/app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 health-services/PassiveData/app/src/main/res/layout/activity_main.xml delete mode 100644 health-services/PassiveData/app/src/main/res/mipmap/ic_launcher.xml delete mode 100644 health-services/PassiveData/app/src/main/res/mipmap/ic_launcher_round.xml delete mode 100644 health-services/PassiveData/app/src/main/res/values/dimens.xml delete mode 100644 health-services/PassiveData/app/src/main/res/values/strings.xml delete mode 100644 health-services/PassiveData/app/src/main/res/values/themes.xml delete mode 100644 health-services/PassiveData/build.gradle delete mode 100644 health-services/PassiveData/gradle.properties delete mode 100644 health-services/PassiveData/gradle/wrapper/gradle-wrapper.jar delete mode 100644 health-services/PassiveData/gradle/wrapper/gradle-wrapper.properties delete mode 100755 health-services/PassiveData/gradlew delete mode 100644 health-services/PassiveData/gradlew.bat delete mode 100644 health-services/PassiveData/screenshots/whs_passive_data_available.png delete mode 100644 health-services/PassiveData/screenshots/whs_passive_data_not_available.png delete mode 100644 health-services/PassiveData/settings.gradle delete mode 100644 health-services/PassiveGoals/README.md delete mode 100644 health-services/PassiveGoals/app/build.gradle delete mode 100644 health-services/PassiveGoals/app/proguard-rules.pro delete mode 100644 health-services/PassiveGoals/app/src/main/AndroidManifest.xml delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/HealthServicesManager.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/MainActivity.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/MainApplication.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/MainModule.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/MainViewModel.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/PassiveGoalService.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/PassiveGoalsRepository.kt delete mode 100644 health-services/PassiveGoals/app/src/main/java/com/example/passivegoals/StartupReceiver.kt delete mode 100644 health-services/PassiveGoals/app/src/main/res/drawable/ic_daily_goal.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/drawable/ic_floors.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/drawable/ic_not_available.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/layout/activity_main.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/mipmap/ic_launcher.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/mipmap/ic_launcher_round.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/values/dimens.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/values/strings.xml delete mode 100644 health-services/PassiveGoals/app/src/main/res/values/themes.xml delete mode 100644 health-services/PassiveGoals/build.gradle delete mode 100644 health-services/PassiveGoals/gradle.properties delete mode 100644 health-services/PassiveGoals/gradle/wrapper/gradle-wrapper.jar delete mode 100644 health-services/PassiveGoals/gradle/wrapper/gradle-wrapper.properties delete mode 100755 health-services/PassiveGoals/gradlew delete mode 100644 health-services/PassiveGoals/gradlew.bat delete mode 100644 health-services/PassiveGoals/screenshots/passive_goals.png delete mode 100644 health-services/PassiveGoals/screenshots/passive_goals_not_available.png delete mode 100644 health-services/PassiveGoals/settings.gradle diff --git a/.github/workflows/ExerciseSample.yml b/.github/workflows/ExerciseSample.yml deleted file mode 100644 index 25ea35a0..00000000 --- a/.github/workflows/ExerciseSample.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: ExerciseSample - -on: - push: - branches: - - main - paths: - - 'health-services/ExerciseSample/**' - - '.github/workflows/ExerciseSample.yml' - pull_request: - paths: - - 'health-services/ExerciseSample/**' - - '.github/workflows/ExerciseSample.yml' - -env: - SAMPLE_PATH: health-services/ExerciseSample - -jobs: - build: - # Skip build if head commit contains 'skip ci' - if: "!contains(github.event.head_commit.message, 'skip ci')" - - runs-on: ubuntu-latest - timeout-minutes: 40 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - - - name: Unit Tests - uses: gradle/gradle-build-action@v3 - with: - build-root-directory: ${{ env.SAMPLE_PATH }} - arguments: testDebug - - - name: Upload test results and reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: build-results - path: | - **/build/test-results/* - **/build/reports/* - **/out/* diff --git a/.github/workflows/MeasureData.yml b/.github/workflows/MeasureData.yml deleted file mode 100644 index 5d504255..00000000 --- a/.github/workflows/MeasureData.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: MeasureData - -on: - push: - branches: - - main - paths: - - 'health-services/MeasureData/**' - - '.github/workflows/MeasureData.yml' - pull_request: - paths: - - 'health-services/MeasureData/**' - - '.github/workflows/MeasureData.yml' - -env: - SAMPLE_PATH: health-services/MeasureData - -jobs: - build: - # Skip build if head commit contains 'skip ci' - if: "!contains(github.event.head_commit.message, 'skip ci')" - - runs-on: ubuntu-latest - timeout-minutes: 40 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - - - name: Unit Tests - uses: gradle/gradle-build-action@v3 - with: - build-root-directory: ${{ env.SAMPLE_PATH }} - arguments: testDebug - - - name: Upload test results and reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: build-results - path: | - **/build/test-results/* - **/build/reports/* - **/out/* diff --git a/.github/workflows/PassiveData.yml b/.github/workflows/PassiveData.yml deleted file mode 100644 index 24bcc6c7..00000000 --- a/.github/workflows/PassiveData.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: PassiveData - -on: - push: - branches: - - main - paths: - - 'health-services/PassiveData/**' - - '.github/workflows/PassiveData.yml' - pull_request: - paths: - - 'health-services/PassiveData/**' - - '.github/workflows/PassiveData.yml' - -env: - SAMPLE_PATH: health-services/PassiveData - -jobs: - build: - # Skip build if head commit contains 'skip ci' - if: "!contains(github.event.head_commit.message, 'skip ci')" - - runs-on: ubuntu-latest - timeout-minutes: 40 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - - - name: Unit Tests - uses: gradle/gradle-build-action@v3 - with: - build-root-directory: ${{ env.SAMPLE_PATH }} - arguments: testDebug - - - name: Upload test results and reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: build-results - path: | - **/build/test-results/* - **/build/reports/* - **/out/* diff --git a/.github/workflows/PassiveGoals.yml b/.github/workflows/PassiveGoals.yml deleted file mode 100644 index e87757b3..00000000 --- a/.github/workflows/PassiveGoals.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: PassiveGoals - -on: - push: - branches: - - main - paths: - - 'health-services/PassiveGoals/**' - - '.github/workflows/PassiveGoals.yml' - pull_request: - paths: - - 'health-services/PassiveGoals/**' - - '.github/workflows/PassiveGoals.yml' - -env: - SAMPLE_PATH: health-services/PassiveGoals - -jobs: - build: - # Skip build if head commit contains 'skip ci' - if: "!contains(github.event.head_commit.message, 'skip ci')" - - runs-on: ubuntu-latest - timeout-minutes: 40 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 17 - - - name: Unit Tests - uses: gradle/gradle-build-action@v3 - with: - build-root-directory: ${{ env.SAMPLE_PATH }} - arguments: testDebug - - - name: Upload test results and reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: build-results - path: | - **/build/test-results/* - **/build/reports/* - **/out/* diff --git a/health-services/ExerciseSample/README.md b/health-services/ExerciseSample/README.md deleted file mode 100644 index 5e61bc1c..00000000 --- a/health-services/ExerciseSample/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Exercise Sample - -This sample demonstrates managing an exercise experience using the `ExerciseClient` API. - -### Running the sample - -You will need a Wear device or emulator with Health Services installed. Open the sample project in -Android Studio and launch the app on your device or emulator. - -On startup, the app checks the device capabilities. If the necessary exercise capabilities are -available, you will see a screen like this: - -![exercise available screenshot](screenshots/exercise_available.png) - -When you press start, the app configures a running exercise and starts it. (You may need to use the -[synthetic data provider](#using-synthetic-data) so that Health Services doesn't immediately pause -the exercise due to no user activity.) While the exercise is active, the UI will show the exercise -duration, heart rate bpm, calories burned, distance traveled, and the number of laps. To mark a lap, -press one of the hardware buttons around the watch frame. - -![exercise in progress screenshot](screenshots/exercise_in_progress.png) - -While an exercise is in progress, if you leave the app, an ongoing notification appears, offering -quick return to the exercise screen. - -![ongoing notification screenshot](screenshots/ongoing_notification.png) - -On devices where the exercise capability is not available, you will see a screen like this: - -![exercise unavailable screenshot](screenshots/exercise_not_available.png) - -### Using synthetic data - -On some devices, you can get Health Services to generate "fake" activity for testing purposes via -the synthetic data provider. See [Use synthetic data with Health Services](https://developer.android.com/training/wearables/health-services/synthetic-data) -for more information. diff --git a/health-services/ExerciseSample/app/build.gradle b/health-services/ExerciseSample/app/build.gradle deleted file mode 100644 index 3e4784ee..00000000 --- a/health-services/ExerciseSample/app/build.gradle +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'dagger.hilt.android.plugin' - id 'kotlin-kapt' -} - -android { - compileSdk 34 - - defaultConfig { - applicationId "com.example.exercise" - minSdk 30 - targetSdk 33 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - buildFeatures { - viewBinding true - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.majorVersion - // Allows to use experimental coroutines APIs like callbackFlow() - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - } - namespace 'com.example.exercise' -} - -dependencies { - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.core:core-ktx:1.13.1' - implementation "androidx.fragment:fragment-ktx:1.8.4" - - // Hilt dependency injection - implementation "com.google.dagger:hilt-android:$hilt_version" - kapt "com.google.dagger:hilt-android-compiler:$hilt_version" - implementation 'androidx.hilt:hilt-work:1.2.0' - kapt 'androidx.hilt:hilt-compiler:1.2.0' - implementation "androidx.work:work-runtime-ktx:2.9.1" - - // Wear - implementation 'androidx.wear:wear:1.3.0' - // Health Services - implementation 'androidx.health:health-services-client:1.0.0-rc02' - // Used to bridge between Futures and coroutines - implementation 'com.google.guava:guava:33.3.1-android' - implementation "androidx.concurrent:concurrent-futures-ktx:1.2.0" - - // Lifecycle - implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" - - // Material - implementation 'com.google.android.material:material:1.12.0' - - // Navigation - implementation "androidx.navigation:navigation-fragment-ktx:2.8.2" - implementation "androidx.navigation:navigation-ui-ktx:2.8.2" - - // Ongoing Activity. - implementation "androidx.wear:wear-ongoing:1.0.0" -} diff --git a/health-services/ExerciseSample/app/proguard-rules.pro b/health-services/ExerciseSample/app/proguard-rules.pro deleted file mode 100644 index 1d416f84..00000000 --- a/health-services/ExerciseSample/app/proguard-rules.pro +++ /dev/null @@ -1,22 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile --keep class * extends com.google.protobuf.GeneratedMessageLite { *; } \ No newline at end of file diff --git a/health-services/ExerciseSample/app/src/main/AndroidManifest.xml b/health-services/ExerciseSample/app/src/main/AndroidManifest.xml deleted file mode 100644 index b2a2642c..00000000 --- a/health-services/ExerciseSample/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseFragment.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseFragment.kt deleted file mode 100644 index eb7e797b..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseFragment.kt +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.content.res.ColorStateList -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.health.services.client.data.DataPointContainer -import androidx.health.services.client.data.DataType -import androidx.health.services.client.data.ExerciseState -import androidx.health.services.client.data.ExerciseUpdate -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.findNavController -import androidx.wear.ambient.AmbientModeSupport -import com.example.exercise.databinding.FragmentExerciseBinding -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import java.time.Duration -import java.time.Instant -import javax.inject.Inject -import kotlin.math.roundToInt - -/** - * Fragment showing the exercise controls and current exercise metrics. - */ -@AndroidEntryPoint -class ExerciseFragment : Fragment() { - - @Inject - lateinit var healthServicesManager: HealthServicesManager - - private val viewModel: MainViewModel by activityViewModels() - - private var _binding: FragmentExerciseBinding? = null - private val binding get() = _binding!! - - private var serviceConnection = ExerciseServiceConnection() - - private var cachedExerciseState = ExerciseState.ENDED - private var activeDurationCheckpoint = - ExerciseUpdate.ActiveDurationCheckpoint(Instant.now(), Duration.ZERO) - private var chronoTickJob: Job? = null - private var uiBindingJob: Job? = null - - private lateinit var ambientController: AmbientModeSupport.AmbientController - private lateinit var ambientModeHandler: AmbientModeHandler - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentExerciseBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.startEndButton.setOnClickListener { - // App could take a perceptible amount of time to transition between states; put button into - // an intermediary "disabled" state to provide UI feedback. - it.isEnabled = false - startEndExercise() - } - binding.pauseResumeButton.setOnClickListener { - // App could take a perceptible amount of time to transition between states; put button into - // an intermediary "disabled" state to provide UI feedback. - it.isEnabled = false - pauseResumeExercise() - } - - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - val capabilities = - healthServicesManager.getExerciseCapabilities() ?: return@repeatOnLifecycle - val supportedTypes = capabilities.supportedDataTypes - - // Set enabled state for relevant text elements. - binding.heartRateText.isEnabled = DataType.HEART_RATE_BPM in supportedTypes - binding.caloriesText.isEnabled = DataType.CALORIES_TOTAL in supportedTypes - binding.distanceText.isEnabled = DataType.DISTANCE in supportedTypes - binding.lapsText.isEnabled = true - } - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.keyPressFlow.collect { - healthServicesManager.markLap() - } - } - } - - // Ambient Mode - ambientModeHandler = AmbientModeHandler() - ambientController = AmbientModeSupport.attach(requireActivity()) - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.ambientEventFlow.collect { - ambientModeHandler.onAmbientEvent(it) - } - } - } - - // Bind to our service. Views will only update once we are connected to it. - ExerciseService.bindService(requireContext().applicationContext, serviceConnection) - bindViewsToService() - } - - override fun onDestroyView() { - super.onDestroyView() - // Unbind from the service. - ExerciseService.unbindService(requireContext().applicationContext, serviceConnection) - _binding = null - } - - private fun startEndExercise() { - if (cachedExerciseState.isEnded) { - tryStartExercise() - } else { - checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - }.endExercise() - } - } - - private fun tryStartExercise() { - viewLifecycleOwner.lifecycleScope.launch { - if (healthServicesManager.isTrackingExerciseInAnotherApp()) { - // Show the user a confirmation screen. - findNavController().navigate(R.id.to_newExerciseConfirmation) - } else if (!healthServicesManager.isExerciseInProgress()) { - checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - }.startExercise() - } - } - } - - private fun pauseResumeExercise() { - val service = checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - } - if (cachedExerciseState.isPaused) { - service.resumeExercise() - } else { - service.pauseExercise() - } - } - - private fun bindViewsToService() { - if (uiBindingJob != null) return - - uiBindingJob = viewLifecycleOwner.lifecycleScope.launch { - serviceConnection.repeatWhenConnected { service -> - // Use separate launch blocks because each .collect executes indefinitely. - launch { - service.exerciseState.collect { - updateExerciseStatus(it) - } - } - launch { - service.latestMetrics.collect { - it?.let { updateMetrics(it) } - } - } - launch { - service.exerciseLaps.collect { - updateLaps(it) - } - } - launch { - service.activeDurationCheckpoint.collect { - // We don't update the chronometer here since these updates come at irregular - // intervals. Instead we store the duration and update the chronometer with - // our own regularly-timed intervals. - activeDurationCheckpoint = it - } - } - } - } - } - - private fun unbindViewsFromService() { - uiBindingJob?.cancel() - uiBindingJob = null - } - - private fun updateExerciseStatus(state: ExerciseState) { - val previousStatus = cachedExerciseState - if (previousStatus.isEnded && !state.isEnded) { - // We're starting a new exercise. Clear metrics from any prior exercise. - resetDisplayedFields() - } - - if (state == ExerciseState.ACTIVE && !ambientController.isAmbient) { - startChronometer() - } else { - stopChronometer() - } - - updateButtons(state) - cachedExerciseState = state - } - - private fun updateButtons(state: ExerciseState) { - binding.startEndButton.setText(if (state.isEnded) R.string.start else R.string.end) - binding.startEndButton.isEnabled = true - binding.pauseResumeButton.setText(if (state.isPaused) R.string.resume else R.string.pause) - binding.pauseResumeButton.isEnabled = !state.isEnded - } - - private fun updateMetrics(latestMetrics: DataPointContainer) { - latestMetrics.getData(DataType.HEART_RATE_BPM).let { - if (it.isNotEmpty()) { - binding.heartRateText.text = it.last().value.roundToInt().toString() - } - } - latestMetrics.getData(DataType.DISTANCE_TOTAL)?.let { - binding.distanceText.text = formatDistanceKm(it.total) - } - latestMetrics.getData(DataType.CALORIES_TOTAL)?.let { - binding.caloriesText.text = formatCalories(it.total) - } - } - - private fun updateLaps(laps: Int) { - binding.lapsText.text = laps.toString() - } - - private fun startChronometer() { - if (chronoTickJob == null) { - chronoTickJob = viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - while (true) { - delay(CHRONO_TICK_MS) - updateChronometer() - } - } - } - } - } - - private fun stopChronometer() { - chronoTickJob?.cancel() - chronoTickJob = null - } - - private fun updateChronometer() { - // We update the chronometer on our own regular intervals independent of the exercise - // duration value received. If the exercise is still active, add the difference between - // the last duration update and now. - val duration = activeDurationCheckpoint.displayDuration(Instant.now(), cachedExerciseState) - binding.elapsedTime.text = formatElapsedTime(duration, !ambientController.isAmbient) - } - - private fun resetDisplayedFields() { - getString(R.string.empty_metric).let { - binding.heartRateText.text = it - binding.caloriesText.text = it - binding.distanceText.text = it - binding.lapsText.text = it - } - binding.elapsedTime.text = formatElapsedTime(Duration.ZERO, true) - } - - // -- Ambient Mode support - - private fun setAmbientUiState(isAmbient: Boolean) { - // Change icons to white while in ambient mode. - val iconTint = if (isAmbient) { - Color.WHITE - } else { - resources.getColor(R.color.primary_orange, null) - } - ColorStateList.valueOf(iconTint).let { - binding.clockIcon.imageTintList = it - binding.heartRateIcon.imageTintList = it - binding.caloriesIcon.imageTintList = it - binding.distanceIcon.imageTintList = it - binding.lapsIcon.imageTintList = it - } - - // Hide the buttons in ambient mode. - val buttonVisibility = if (isAmbient) View.INVISIBLE else View.VISIBLE - buttonVisibility.let { - binding.startEndButton.visibility = it - binding.pauseResumeButton.visibility = it - } - } - - private fun performOneTimeUiUpdate() { - val service = checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - } - updateExerciseStatus(service.exerciseState.value) - updateLaps(service.exerciseLaps.value) - - service.latestMetrics.value?.let { updateMetrics(it) } - - activeDurationCheckpoint = service.activeDurationCheckpoint.value - updateChronometer() - } - - inner class AmbientModeHandler { - internal fun onAmbientEvent(event: AmbientEvent) { - when (event) { - is AmbientEvent.Enter -> onEnterAmbient() - is AmbientEvent.Exit -> onExitAmbient() - is AmbientEvent.Update -> onUpdateAmbient() - } - } - - private fun onEnterAmbient() { - // Note: Apps should also handle low-bit ambient and burn-in protection. - unbindViewsFromService() - setAmbientUiState(true) - performOneTimeUiUpdate() - } - - private fun onExitAmbient() { - performOneTimeUiUpdate() - setAmbientUiState(false) - bindViewsToService() - } - - private fun onUpdateAmbient() { - performOneTimeUiUpdate() - } - } - - private companion object { - const val CHRONO_TICK_MS = 200L - } -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseService.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseService.kt deleted file mode 100644 index 9b4f13a6..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseService.kt +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.os.Binder -import android.os.IBinder -import android.os.SystemClock -import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.health.services.client.data.* -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleService -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.NavDeepLinkBuilder -import androidx.wear.ongoing.OngoingActivity -import androidx.wear.ongoing.Status -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import java.time.Duration -import java.time.Instant -import javax.inject.Inject - -/** - * Service that listens to the exercise state of the app. Local clients can bind to this service to - * access the exercise state and associated metrics. - * - * This service manages its own lifecycle. Once a client binds to it, it calls [startService] on - * itself and registers for exercise state. When there are no bound clients, if there isn't an - * active exercise, this service stops itself. If there is an active exercise, it moves itself to - * the foreground with an ongoing notification so that the user has an easy way back into the app. - * - * (see [Bound Services](https://developer.android.com/guide/components/bound-services)) - */ -@AndroidEntryPoint -class ExerciseService : LifecycleService() { - - @Inject - lateinit var healthServicesManager: HealthServicesManager - - private val localBinder = LocalBinder() - private var isBound = false - private var isStarted = false - private var isForeground = false - - private val _exerciseState = MutableStateFlow(ExerciseState.ENDED) - val exerciseState: StateFlow = _exerciseState - - private val _latestMetrics = MutableStateFlow(null) - val latestMetrics: StateFlow = _latestMetrics - - private val _exerciseLaps = MutableStateFlow(0) - val exerciseLaps: StateFlow = _exerciseLaps - - private val _activeDurationCheckpoint = - MutableStateFlow( - ExerciseUpdate.ActiveDurationCheckpoint(Instant.now(), Duration.ZERO) - ) - val activeDurationCheckpoint: - StateFlow = _activeDurationCheckpoint - - private val _locationAvailabilityState = MutableStateFlow(LocationAvailability.UNKNOWN) - val locationAvailabilityState: StateFlow = _locationAvailabilityState - - /** - * Prepare exercise in this service's coroutine context. - */ - fun prepareExercise() { - lifecycleScope.launch { - healthServicesManager.prepareExercise() - } - } - - /** - * Start exercise in this service's coroutine context. - */ - fun startExercise() { - lifecycleScope.launch { - healthServicesManager.startExercise() - } - postOngoingActivityNotification() - } - - /** - * Pause exercise in this service's coroutine context. - */ - fun pauseExercise() { - lifecycleScope.launch { - healthServicesManager.pauseExercise() - } - } - - /** - * Resume exercise in this service's coroutine context. - */ - fun resumeExercise() { - lifecycleScope.launch { - healthServicesManager.resumeExercise() - } - } - - /** - * End exercise in this service's coroutine context. - */ - fun endExercise() { - lifecycleScope.launch { - healthServicesManager.endExercise() - } - removeOngoingActivityNotification() - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - super.onStartCommand(intent, flags, startId) - Log.d(TAG, "onStartCommand") - - if (!isStarted) { - isStarted = true - - if (!isBound) { - // We may have been restarted by the system. Manage our lifetime accordingly. - stopSelfIfNotRunning() - } - - // Start collecting exercise information. We might stop shortly (see above), in which - // case launchWhenStarted takes care of canceling this coroutine. - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - healthServicesManager.exerciseUpdateFlow.collect { - when (it) { - is ExerciseMessage.ExerciseUpdateMessage -> - processExerciseUpdate(it.exerciseUpdate) - is ExerciseMessage.LapSummaryMessage -> - _exerciseLaps.value = it.lapSummary.lapCount - is ExerciseMessage.LocationAvailabilityMessage -> - _locationAvailabilityState.value = it.locationAvailability - } - } - } - } - } - } - - // If our process is stopped, we might have an active exercise. We want the system to - // recreate our service so that we can present the ongoing notification in that case. - return Service.START_STICKY - } - - private fun stopSelfIfNotRunning() { - lifecycleScope.launch { - // We may have been restarted by the system. Check for an ongoing exercise. - if (!healthServicesManager.isExerciseInProgress()) { - // Need to cancel [prepareExercise()] to prevent battery drain. - if (_exerciseState.value == ExerciseState.PREPARING) { - lifecycleScope.launch { - healthServicesManager.endExercise() - } - } - - // We have nothing to do, so we can stop. - stopSelf() - } - } - } - - private fun processExerciseUpdate(exerciseUpdate: ExerciseUpdate) { - val oldState = _exerciseState.value - if (!oldState.isEnded && exerciseUpdate.exerciseStateInfo.state.isEnded) { - // Our exercise ended. Gracefully handle this termination be doing the following: - // TODO Save partial workout state, show workout summary, and let the user know why the exercise was ended. - - // Dismiss any ongoing activity notification. - removeOngoingActivityNotification() - - // Custom flow for the possible states captured by the isEnded boolean - when (exerciseUpdate.exerciseStateInfo.endReason) { - ExerciseEndReason.AUTO_END_SUPERSEDED -> { - // TODO Send the user a notification (another app ended their workout) - Log.i( - TAG, - "Your exercise was terminated because another app started tracking an exercise" - ) - } - ExerciseEndReason.AUTO_END_MISSING_LISTENER -> { - // TODO Send the user a notification - Log.i( - TAG, - "Your exercise was auto ended because there were no registered listeners" - ) - } - ExerciseEndReason.AUTO_END_PERMISSION_LOST -> { - // TODO Send the user a notification - Log.w( - TAG, - "Your exercise was auto ended because it lost the required permissions" - ) - } - else -> { - } - } - } else if (oldState.isEnded && exerciseUpdate.exerciseStateInfo.state == ExerciseState.ACTIVE) { - // Reset laps. - _exerciseLaps.value = 0 - } - - _exerciseState.value = exerciseUpdate.exerciseStateInfo.state - _latestMetrics.value = exerciseUpdate.latestMetrics - exerciseUpdate.activeDurationCheckpoint?.let { _activeDurationCheckpoint.value = it } - } - - override fun onBind(intent: Intent): IBinder { - super.onBind(intent) - handleBind() - return localBinder - } - - override fun onRebind(intent: Intent?) { - super.onRebind(intent) - handleBind() - } - - private fun handleBind() { - if (!isBound) { - isBound = true - // Start ourself. This will begin collecting exercise state if we aren't already. - startService(Intent(this, this::class.java)) - } - } - - override fun onUnbind(intent: Intent?): Boolean { - isBound = false - lifecycleScope.launch { - // Client can unbind because it went through a configuration change, in which case it - // will be recreated and bind again shortly. Wait a few seconds, and if still not bound, - // manage our lifetime accordingly. - delay(UNBIND_DELAY_MILLIS) - if (!isBound) { - stopSelfIfNotRunning() - } - } - // Allow clients to re-bind. We will be informed of this in onRebind(). - return true - } - - private fun removeOngoingActivityNotification() { - if (isForeground) { - Log.d(TAG, "Removing ongoing activity notification") - isForeground = false - stopForeground(STOP_FOREGROUND_REMOVE) - } - } - - private fun postOngoingActivityNotification() { - if (!isForeground) { - isForeground = true - Log.d(TAG, "Posting ongoing activity notification") - - createNotificationChannel() - startForeground(NOTIFICATION_ID, buildNotification()) - } - } - - private fun createNotificationChannel() { - val notificationChannel = NotificationChannel( - NOTIFICATION_CHANNEL, - NOTIFICATION_CHANNEL_DISPLAY, - NotificationManager.IMPORTANCE_DEFAULT - ) - val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - manager.createNotificationChannel(notificationChannel) - } - - private fun buildNotification(): Notification { - // Make an intent that will take the user straight to the exercise UI. - val pendingIntent = NavDeepLinkBuilder(this) - .setGraph(R.navigation.nav_graph) - .setDestination(R.id.exerciseFragment) - .createPendingIntent() - // Build the notification. - val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL) - .setContentTitle(NOTIFICATION_TITLE) - .setContentText(NOTIFICATION_TEXT) - .setSmallIcon(R.drawable.ic_run) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_WORKOUT) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - - // Ongoing Activity allows an ongoing Notification to appear on additional surfaces in the - // Wear OS user interface, so that users can stay more engaged with long running tasks. - val duration = - activeDurationCheckpoint.value.displayDuration(Instant.now(), exerciseState.value) - val startMillis = SystemClock.elapsedRealtime() - duration.toMillis() - val ongoingActivityStatus = Status.Builder() - .addTemplate(ONGOING_STATUS_TEMPLATE) - .addPart("duration", Status.StopwatchPart(startMillis)) - .build() - val ongoingActivity = - OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder) - .setAnimatedIcon(R.drawable.ic_run) - .setStaticIcon(R.drawable.ic_run) - .setTouchIntent(pendingIntent) - .setStatus(ongoingActivityStatus) - .build() - ongoingActivity.apply(applicationContext) - - return notificationBuilder.build() - } - - /** Local clients will use this to access the service. */ - inner class LocalBinder : Binder() { - fun getService() = this@ExerciseService - } - - companion object { - private const val NOTIFICATION_ID = 1 - private const val NOTIFICATION_CHANNEL = "com.example.exercise.ONGOING_EXERCISE" - private const val NOTIFICATION_CHANNEL_DISPLAY = "Ongoing Exercise" - private const val NOTIFICATION_TITLE = "Exercise Sample" - private const val NOTIFICATION_TEXT = "Ongoing Exercise" - private const val ONGOING_STATUS_TEMPLATE = "Ongoing Exercise #duration#" - private const val UNBIND_DELAY_MILLIS = 3_000L - - fun bindService(context: Context, serviceConnection: ServiceConnection) { - val serviceIntent = Intent(context, ExerciseService::class.java) - context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE) - } - - fun unbindService(context: Context, serviceConnection: ServiceConnection) { - context.unbindService(serviceConnection) - } - } -} - -fun ExerciseUpdate.ActiveDurationCheckpoint.displayDuration( - now: Instant, - state: ExerciseState -): Duration { - val isActive = with(state) { - !isEnded && !isPaused && !isEnding && !isResuming - } - val extraDuration = if (isActive) Duration.between(this.time, now) else Duration.ZERO - return this.activeDuration.plus(extraDuration) -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseServiceConnection.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseServiceConnection.kt deleted file mode 100644 index eda48135..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseServiceConnection.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.content.ComponentName -import android.content.ServiceConnection -import android.os.IBinder -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.CoroutineScope - -/** - * ServiceConnection implementation. This is implemented as a LifecycleOwner so we can launch - * coroutines bound to its lifecycle. When the service is connected, the lifecycle will be - * [Lifecycle.State.STARTED]; otherwise it will be [Lifecycle.State.INITIALIZED]. - */ -class ExerciseServiceConnection : ServiceConnection, LifecycleOwner { - - var exerciseService: ExerciseService? = null - - override val lifecycle = LifecycleRegistry(this).apply { - currentState = Lifecycle.State.INITIALIZED - } - - override fun onServiceConnected(name: ComponentName, service: IBinder) { - exerciseService = (service as ExerciseService.LocalBinder).getService() - lifecycle.currentState = Lifecycle.State.STARTED - } - - override fun onServiceDisconnected(name: ComponentName) { - lifecycle.currentState = Lifecycle.State.INITIALIZED - } - - /** - * Runs the given [block] in a new coroutine when the service is connected and suspends the - * execution until this Lifecycle is [Lifecycle.State.DESTROYED]. The [block] will cancel and - * re-launch as the service becomes connected or disconnected. The connected service is passed - * to the [block] so that clients can interact with it. - */ - suspend fun repeatWhenConnected(block: suspend CoroutineScope.(ExerciseService) -> Unit) { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - block(exerciseService as ExerciseService) - } - } -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/FormattingUtils.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/FormattingUtils.kt deleted file mode 100644 index b5d0961b..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/FormattingUtils.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.text.style.RelativeSizeSpan -import androidx.core.text.buildSpannedString -import androidx.core.text.inSpans -import java.time.Duration -import java.util.concurrent.TimeUnit -import kotlin.math.roundToInt - -private const val UNITS_RELATIVE_SIZE = .6f -private val MINUTES_PER_HOUR = TimeUnit.HOURS.toMinutes(1) -private val SECONDS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1) - -/** - * Format an elapsed duration as `01m01s`. Hours are shown if present, e.g. `1h01m01s`. If - * [includeSeconds] is `false`, seconds are omitted, e.g. `01m` or `1h01m`. - */ -fun formatElapsedTime(elapsedDuration: Duration, includeSeconds: Boolean) = buildSpannedString { - val hours = elapsedDuration.toHours() - if (hours > 0) { - append(hours.toString()) - inSpans(RelativeSizeSpan(UNITS_RELATIVE_SIZE)) { - append("h") - } - } - val minutes = elapsedDuration.toMinutes() % MINUTES_PER_HOUR - append("%02d".format(minutes)) - inSpans(RelativeSizeSpan(UNITS_RELATIVE_SIZE)) { - append("m") - } - if (includeSeconds) { - val seconds = elapsedDuration.seconds % SECONDS_PER_MINUTE - append("%02d".format(seconds)) - inSpans(RelativeSizeSpan(UNITS_RELATIVE_SIZE)) { - append("s") - } - } -} - -/** Format a distance to two decimals with a "km" suffix. */ -fun formatDistanceKm(meters: Double) = buildSpannedString { - append("%02.2f".format(meters / 1_000)) - inSpans(RelativeSizeSpan(UNITS_RELATIVE_SIZE)) { - append("km") - } -} - -/** Format calories burned to an integer with a "cal" suffix. */ -fun formatCalories(calories: Double) = buildSpannedString { - append(calories.roundToInt().toString()) - inSpans(RelativeSizeSpan(UNITS_RELATIVE_SIZE)) { - append(" cal") - } -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/HealthServicesManager.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/HealthServicesManager.kt deleted file mode 100644 index 455ed46a..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/HealthServicesManager.kt +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.util.Log -import androidx.health.services.client.* -import androidx.health.services.client.data.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.channels.trySendBlocking -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.runBlocking -import javax.inject.Inject - -/** - * Entry point for [HealthServicesClient] APIs, wrapping them in coroutine-friendly APIs. - */ -class HealthServicesManager @Inject constructor( - healthServicesClient: HealthServicesClient, - coroutineScope: CoroutineScope -) { - private val exerciseClient = healthServicesClient.exerciseClient - - private var exerciseCapabilities: ExerciseTypeCapabilities? = null - private var capabilitiesLoaded = false - - suspend fun getExerciseCapabilities(): ExerciseTypeCapabilities? { - if (!capabilitiesLoaded) { - val capabilities = exerciseClient.getCapabilities() - if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) { - exerciseCapabilities = - capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING) - } - capabilitiesLoaded = true - } - return exerciseCapabilities - } - - suspend fun hasExerciseCapability(): Boolean { - return getExerciseCapabilities() != null - } - - suspend fun isExerciseInProgress(): Boolean { - val exerciseInfo = exerciseClient.getCurrentExerciseInfo() - return exerciseInfo.exerciseTrackedStatus == ExerciseTrackedStatus.OWNED_EXERCISE_IN_PROGRESS - } - - suspend fun isTrackingExerciseInAnotherApp(): Boolean { - val exerciseInfo = exerciseClient.getCurrentExerciseInfo() - return exerciseInfo.exerciseTrackedStatus == ExerciseTrackedStatus.OTHER_APP_IN_PROGRESS - } - - /*** - * Note: don't call this method from outside of foreground service (ie. [ExerciseService]) - * when acquiring calories or distance. - */ - suspend fun startExercise() { - Log.d(TAG, "Starting exercise") - // Types for which we want to receive metrics. Only ask for ones that are supported. - val capabilities = getExerciseCapabilities() ?: return - val dataTypes = setOf( - DataType.HEART_RATE_BPM, - DataType.CALORIES_TOTAL, - DataType.DISTANCE - ).intersect(capabilities.supportedDataTypes) - - val exerciseGoals = mutableListOf>() - if (supportsCalorieGoal(capabilities)) { - // Create a one-time goal. - exerciseGoals.add( - ExerciseGoal.createOneTimeGoal( - DataTypeCondition( - dataType = DataType.CALORIES_TOTAL, - threshold = CALORIES_THRESHOLD, - comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL - ) - ) - ) - } - - if (supportsDistanceMilestone(capabilities)) { - // Create a milestone goal. To make a milestone for every kilometer, set the initial - // threshold to 1km and the period to 1km. - exerciseGoals.add( - ExerciseGoal.createMilestone( - condition = DataTypeCondition( - dataType = DataType.DISTANCE_TOTAL, - threshold = DISTANCE_THRESHOLD, - comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL - ), - period = DISTANCE_THRESHOLD - ) - ) - } - - val supportsAutoPauseAndResume = capabilities.supportsAutoPauseAndResume - - val config = ExerciseConfig( - exerciseType = ExerciseType.RUNNING, - dataTypes = dataTypes, - isAutoPauseAndResumeEnabled = supportsAutoPauseAndResume, - isGpsEnabled = true, - exerciseGoals = exerciseGoals - ) - exerciseClient.startExercise(config) - } - - private fun supportsCalorieGoal(capabilities: ExerciseTypeCapabilities): Boolean { - val supported = capabilities.supportedGoals[DataType.CALORIES_TOTAL] - return supported != null && ComparisonType.GREATER_THAN_OR_EQUAL in supported - } - - private fun supportsDistanceMilestone(capabilities: ExerciseTypeCapabilities): Boolean { - val supported = capabilities.supportedMilestones[DataType.DISTANCE_TOTAL] - return supported != null && ComparisonType.GREATER_THAN_OR_EQUAL in supported - } - - /*** - * Note: don't call this method from outside of [ExerciseService] - * when acquiring calories or distance. - */ - suspend fun prepareExercise() { - Log.d(TAG, "Preparing an exercise") - - // TODO Handle various exerciseTrackedStatus states, especially OWNED_EXERCISE_IN_PROGRESS - // and OTHER_APP_IN_PROGRESS - - val warmUpConfig = WarmUpConfig( - ExerciseType.RUNNING, - setOf( - DataType.HEART_RATE_BPM, - DataType.LOCATION - ) - ) - - try { - exerciseClient.prepareExercise(warmUpConfig) - } catch (e: Exception) { - Log.e(TAG, "Prepare exercise failed - ${e.message}") - } - } - - suspend fun endExercise() { - Log.d(TAG, "Ending exercise") - exerciseClient.endExercise() - } - - suspend fun pauseExercise() { - Log.d(TAG, "Pausing exercise") - exerciseClient.pauseExercise() - } - - suspend fun resumeExercise() { - Log.d(TAG, "Resuming exercise") - exerciseClient.resumeExercise() - } - - suspend fun markLap() { - if (isExerciseInProgress()) { - exerciseClient.markLap() - } - } - - /** - * A flow for [ExerciseUpdate]s. - * - * When the flow starts, it will register an [ExerciseUpdateCallback] and start to emit - * messages. - * - * [callbackFlow] is used to bridge between a callback-based API and Kotlin flows. - */ - @OptIn(ExperimentalCoroutinesApi::class) - val exerciseUpdateFlow = callbackFlow { - val callback = object : ExerciseUpdateCallback { - override fun onExerciseUpdateReceived(update: ExerciseUpdate) { - coroutineScope.runCatching { - trySendBlocking(ExerciseMessage.ExerciseUpdateMessage(update)) - } - } - - override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) { - coroutineScope.runCatching { - trySendBlocking(ExerciseMessage.LapSummaryMessage(lapSummary)) - } - } - - override fun onRegistered() { - } - - override fun onRegistrationFailed(throwable: Throwable) { - TODO("Not yet implemented") - } - - override fun onAvailabilityChanged( - dataType: DataType<*, *>, - availability: Availability - ) { - if (availability is LocationAvailability) { - coroutineScope.runCatching { - trySendBlocking(ExerciseMessage.LocationAvailabilityMessage(availability)) - } - } - } - } - exerciseClient.setUpdateCallback(callback) - awaitClose { - runBlocking { - exerciseClient.clearUpdateCallback(callback) - } - } - } - - private companion object { - const val CALORIES_THRESHOLD = 250.0 - const val DISTANCE_THRESHOLD = 1_000.0 // meters - } -} - -sealed class ExerciseMessage { - class ExerciseUpdateMessage(val exerciseUpdate: ExerciseUpdate) : ExerciseMessage() - class LapSummaryMessage(val lapSummary: ExerciseLapSummary) : ExerciseMessage() - class LocationAvailabilityMessage(val locationAvailability: LocationAvailability) : - ExerciseMessage() -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainActivity.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainActivity.kt deleted file mode 100644 index e834f075..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainActivity.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.os.Bundle -import android.view.KeyEvent -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.wear.ambient.AmbientModeSupport -import androidx.wear.ambient.AmbientModeSupport.AmbientCallbackProvider -import dagger.hilt.android.AndroidEntryPoint - -/** - * This Activity serves a handful of functions: - * - to host a [NavHostFragment] - * - to capture KeyEvents - * - to support Ambient Mode, because [AmbientCallbackProvider] must be an `Activity`. - * - * [MainViewModel] is used to coordinate between this Activity and the [ExerciseFragment], which - * contains UI during an active exercise. - */ -@AndroidEntryPoint -class MainActivity : AppCompatActivity(R.layout.activity_main), AmbientCallbackProvider { - - private val viewModel: MainViewModel by viewModels() - - override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment) - return navController.navigateUp() - } - - override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { - return when (keyCode) { - KeyEvent.KEYCODE_STEM_1, - KeyEvent.KEYCODE_STEM_2, - KeyEvent.KEYCODE_STEM_3, - KeyEvent.KEYCODE_STEM_PRIMARY -> { - viewModel.sendKeyPress() - true - } - else -> super.onKeyUp(keyCode, event) - } - } - - override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = AmbientModeCallback() - - inner class AmbientModeCallback : AmbientModeSupport.AmbientCallback() { - override fun onEnterAmbient(ambientDetails: Bundle) { - viewModel.sendAmbientEvent(AmbientEvent.Enter(ambientDetails)) - } - - override fun onExitAmbient() { - viewModel.sendAmbientEvent(AmbientEvent.Exit) - } - - override fun onUpdateAmbient() { - viewModel.sendAmbientEvent(AmbientEvent.Update) - } - } -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainApplication.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainApplication.kt deleted file mode 100644 index 72cf3b62..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainApplication.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.app.Application -import dagger.hilt.android.HiltAndroidApp - -/** - * Application class, needed to enable dependency injection with Hilt. - */ -@HiltAndroidApp -class MainApplication : Application() - -const val TAG = "Exercise Sample" diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainModule.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainModule.kt deleted file mode 100644 index be3bbab0..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainModule.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.content.Context -import androidx.health.services.client.HealthServices -import androidx.health.services.client.HealthServicesClient -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import javax.inject.Singleton - -/** - * Hilt module that provides singleton (application-scoped) objects. - */ -@Module -@InstallIn(SingletonComponent::class) -class MainModule { - @Singleton - @Provides - fun provideHealthServicesClient(@ApplicationContext context: Context): HealthServicesClient = - HealthServices.getClient(context) - - @Singleton - @Provides - fun provideApplicationCoroutineScope(): CoroutineScope = - CoroutineScope(SupervisorJob() + Dispatchers.Default) -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainViewModel.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainViewModel.kt deleted file mode 100644 index 32d561b7..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/MainViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.os.Bundle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * Coordinates messages between [MainActivity] and [ExerciseFragment]. - */ -@HiltViewModel -class MainViewModel @Inject constructor() : ViewModel() { - - private val _ambientEventChannel = Channel(capacity = Channel.CONFLATED) - val ambientEventFlow = _ambientEventChannel.receiveAsFlow() - - private val _keyPressChannel = Channel(capacity = Channel.CONFLATED) - val keyPressFlow = _keyPressChannel.receiveAsFlow() - - fun sendAmbientEvent(event: AmbientEvent) { - viewModelScope.launch { - _ambientEventChannel.send(event) - } - } - - fun sendKeyPress() { - viewModelScope.launch { - _keyPressChannel.send(Unit) - } - } -} - -sealed class AmbientEvent { - class Enter(val ambientDetails: Bundle) : AmbientEvent() - object Exit : AmbientEvent() - object Update : AmbientEvent() -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/PrepareFragment.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/PrepareFragment.kt deleted file mode 100644 index e5103adc..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/PrepareFragment.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.example.exercise - -import android.Manifest -import android.os.Build -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.activity.result.contract.ActivityResultContracts -import androidx.fragment.app.Fragment -import androidx.health.services.client.data.LocationAvailability -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.findNavController -import com.example.exercise.databinding.FragmentPrepareBinding -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -@AndroidEntryPoint -class PrepareFragment : Fragment(R.layout.fragment_prepare) { - private var serviceConnection = ExerciseServiceConnection() - private var _binding: FragmentPrepareBinding? = null - private val binding get() = _binding!! - - private val permissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions() - ) { result -> - if (result.all { it.value }) { - Log.i(TAG, "All required permissions granted") - - viewLifecycleOwner.lifecycleScope.launch { - // Await binding of ExerciseService, since it takes a bit of time - // to instantiate the service. - serviceConnection.repeatWhenConnected { - checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - }.prepareExercise() - } - } - } else { - Log.w(TAG, "Not all required permissions granted") - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentPrepareBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // Bind to our service. Views will only update once we are connected to it. - ExerciseService.bindService(requireContext().applicationContext, serviceConnection) - bindViewsToService() - - binding.startButton.setOnClickListener { - checkNotNull(serviceConnection.exerciseService) { - "Failed to achieve ExerciseService instance" - }.startExercise() - findNavController().navigate(R.id.exerciseFragment) - } - // Check permissions first. - Log.d(TAG, "Checking permissions") - permissionLauncher.launch(REQUIRED_PERMISSIONS) - } - - override fun onDestroyView() { - super.onDestroyView() - // Unbind from the service. - ExerciseService.unbindService(requireContext().applicationContext, serviceConnection) - _binding = null - } - - private fun bindViewsToService() { - viewLifecycleOwner.lifecycleScope.launch { - serviceConnection.repeatWhenConnected { service -> - service.locationAvailabilityState.collect { - updatePrepareLocationStatus(it) - } - } - } - } - - private fun updatePrepareLocationStatus(locationAvailability: LocationAvailability) { - val gpsText = when (locationAvailability) { - LocationAvailability.ACQUIRED_TETHERED, - LocationAvailability.ACQUIRED_UNTETHERED -> R.string.gps_acquired - LocationAvailability.NO_GNSS -> R.string.gps_disabled // TODO Consider redirecting user to change device settings in this case - LocationAvailability.ACQUIRING -> R.string.gps_acquiring - else -> R.string.gps_unavailable - } - binding.gpsStatus.setText(gpsText) - - if (locationAvailability == LocationAvailability.ACQUIRING) { - if (!binding.progressAcquiring.isAnimating) { - binding.progressAcquiring.visibility = View.VISIBLE - } - } else { - if (binding.progressAcquiring.isAnimating) { - binding.progressAcquiring.visibility = View.INVISIBLE - } - } - } - - private companion object { - val REQUIRED_PERMISSIONS = arrayOf( - Manifest.permission.BODY_SENSORS, - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACTIVITY_RECOGNITION - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - arrayOf(Manifest.permission.POST_NOTIFICATIONS) - } else { - emptyArray() - } - } -} diff --git a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/StartupFragment.kt b/health-services/ExerciseSample/app/src/main/java/com/example/exercise/StartupFragment.kt deleted file mode 100644 index dcf70c43..00000000 --- a/health-services/ExerciseSample/app/src/main/java/com/example/exercise/StartupFragment.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.example.exercise - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.findNavController -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * Fragment shown at startup. It's only function is to advance to the next screen based on whether - * the exercise capability is available. - */ -@AndroidEntryPoint -class StartupFragment : Fragment(R.layout.fragment_startup) { - - @Inject - lateinit var healthServicesManager: HealthServicesManager - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - val destination = if (healthServicesManager.hasExerciseCapability()) { - R.id.prepareFragment - } else { - R.id.notAvailableFragment - } - findNavController().navigate(destination) - } - } - } -} - -/** - * Fragment shown when exercise capability is not available. - */ -@AndroidEntryPoint -class NotAvailableFragment : Fragment(R.layout.fragment_not_available) diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_calories_burned.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_calories_burned.xml deleted file mode 100644 index 4953c115..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_calories_burned.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_cancel.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_cancel.xml deleted file mode 100644 index 34aaf15d..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_cancel.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_check.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_check.xml deleted file mode 100644 index bc120df5..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_check.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_clock.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_clock.xml deleted file mode 100644 index 0f696e48..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_clock.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_distance.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_distance.xml deleted file mode 100644 index 61abee43..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_distance.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_heart_outline.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_heart_outline.xml deleted file mode 100644 index 7a0eac5d..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_heart_outline.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_lap.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_lap.xml deleted file mode 100644 index 31978d87..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_lap.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_background.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index bad966e7..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_foreground.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index eca205d4..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_not_available.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_not_available.xml deleted file mode 100644 index 953c78ce..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_not_available.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/drawable/ic_run.xml b/health-services/ExerciseSample/app/src/main/res/drawable/ic_run.xml deleted file mode 100644 index e0b636cf..00000000 --- a/health-services/ExerciseSample/app/src/main/res/drawable/ic_run.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/layout/activity_main.xml b/health-services/ExerciseSample/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 93c94e4c..00000000 --- a/health-services/ExerciseSample/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - diff --git a/health-services/ExerciseSample/app/src/main/res/layout/fragment_exercise.xml b/health-services/ExerciseSample/app/src/main/res/layout/fragment_exercise.xml deleted file mode 100644 index e379ac7e..00000000 --- a/health-services/ExerciseSample/app/src/main/res/layout/fragment_exercise.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -