添加运动路线

本指南与 Health Connect 1.1.0-alpha12 版本兼容。

运动路线允许用户跟踪相关运动活动的 GPS 路线,并与其他应用共享其锻炼地图。

功能可用性

要确定用户的设备是否支持 Health Connect 上的运动路线,请检查客户端上 FEATURE_PLANNED_EXERCISE 的可用性

if (healthConnectClient
     .features
     .getFeatureStatus(
       HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
     ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

  // Feature is available
} else {
  // Feature isn't available
}

如需了解详情,请参阅检查功能可用性

本指南提供了如何向用户请求权限以及应用如何获得写入路线数据作为运动会话一部分的权限的信息。

运动路线的读写功能包括:

  1. 应用为运动路线创建新的写入权限。
  2. 通过将运动会话作为其字段写入路线来插入。
  3. 读取
    1. 对于会话所有者,数据通过会话读取进行访问。
    2. 从第三方应用,通过一个对话框允许用户一次性读取路线。

如果用户没有写入权限且路线未设置,则路线不会更新。

如果您的应用具有路线写入权限,并尝试通过传递不带路线的会话对象来更新会话,则现有路线将被删除。

所需权限

访问运动路线受以下权限保护:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE

要为您的应用添加运动路线功能,首先要请求特定数据类型的写入权限。

您还需要声明一个运动权限,因为每条路线都与一个运动会话相关联(一个会话 = 一次锻炼)。

以下是您需要声明才能写入运动路线的权限:

<application>
  <uses-permission
android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
  <uses-permission
android:name="android.permission.health.WRITE_EXERCISE" />
...
</application>

要读取运动路线,您需要请求以下权限:

<application>
  <uses-permission
android:name="android.permission.health.READ_EXERCISE_ROUTES" />
  <uses-permission
android:name="android.permission.health.READ_EXERCISE" />
...
</application>

要请求权限,请在您首次将应用连接到 Health Connect 时使用 PermissionController.createRequestPermissionResultContract() 方法。您可能希望请求的几个权限是:

  • 读取健康数据,包括路线数据:HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • 写入健康数据,包括路线数据:HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • 写入运动路线数据:HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

向用户请求权限

创建客户端实例后,您的应用需要向用户请求权限。用户必须被允许随时授予或拒绝权限。

为此,请为所需数据类型创建一组权限。请确保在您的 Android 清单中首先声明该集合中的权限。

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(ExerciseSessionRecord::class),
  HealthPermission.getWritePermission(ExerciseSessionRecord::class)
)

使用 getGrantedPermissions 查看您的应用是否已获得所需的权限。如果没有,请使用 createRequestPermissionResultContract 请求这些权限。这将显示 Health Connect 权限屏幕。

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

由于用户可以随时授予或撤销权限,您的应用需要定期检查已授予的权限,并处理权限丢失的情况。

运动会话记录中包含的信息

每个运动会话记录都包含以下信息:

  • 运动类型,例如骑行。
  • 运动路线,其中包含纬度、经度和海拔等信息。

支持的聚合

此数据类型不支持任何聚合。

用法示例

从会话请求路线或写入路线。

从会话请求路线

以下是如何在 Health Connect 中读取会话并从该会话请求路线:

suspend fun readExerciseSessionAndRoute() {
    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofHours(1))

    val grantedPermissions =
        healthConnectClient.permissionController.getGrantedPermissions()
    if (!grantedPermissions.contains(
          HealthPermission.getReadPermission(ExerciseSessionRecord::class))) {
        // The user doesn't allow the app to read exercise session data.
        return
    }

    val readResponse =
      healthConnectClient.readRecords(
        ReadRecordsRequest(
          ExerciseSessionRecord::class,
          TimeRangeFilter.between(startTime, endTime)
        )
      )
    val exerciseRecord = readResponse.records.first()
    val recordId = exerciseRecord.metadata.id

    // See https://developer.android.com/training/basics/intents/result#launch
    // for appropriately handling ActivityResultContract.
    val requestExerciseRouteLauncher = fragment.registerForActivityResul
    (ExerciseRouteRequestContract()) { exerciseRoute: ExerciseRoute? ->
            if (exerciseRoute != null) {
                displayExerciseRoute(exerciseRoute)
            } else {
                // Consent was denied
            }
        }

    val exerciseSessionRecord =
      healthConnectClient.readRecord(ExerciseSessionRecord::class, recordId).record

    when (val exerciseRouteResult = exerciseSessionRecord.exerciseRouteResult) {
        is ExerciseRouteResult.Data ->
            displayExerciseRoute(exerciseRouteResult.exerciseRoute)
        is ExerciseRouteResult.ConsentRequired ->
            requestExerciseRouteLauncher.launch(recordId)
        is ExerciseRouteResult.NoData -> Unit // No exercise route to show
        else -> Unit
    }
  }

  fun displayExerciseRoute(route: ExerciseRoute?) {
    val locations = route.route.orEmpty()
    for (location in locations) {
      // Handle location.
    }
  }

从会话写入路线

以下代码演示了如何记录包含运动路线的会话:

suspend fun InsertExerciseRoute(healthConnectClient: HealthConnectClient) {
    val grantedPermissions =
        healthConnectClient.permissionController.getGrantedPermissions()
    if (!grantedPermissions.contains(
          getWritePermission(ExerciseSessionRecord::class))) {
        // The user doesn't allow the app to write exercise session data.
        return
    }

    val sessionStartTime = Instant.now()
    val sessionDuration = Duration.ofMinutes(20)
    val sessionEndTime = sessionStartTime.plus(sessionDuration)

    val exerciseRoute =
        if (grantedPermissions.contains(PERMISSION_WRITE_EXERCISE_ROUTE)) ExerciseRoute(
            listOf(
                ExerciseRoute.Location(
                    // Location times must be on or after the session start time
                    time = sessionStartTime,
                    latitude = 6.5483,
                    longitude = 0.5488,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.0),
                ), ExerciseRoute.Location(
                    // Location times must be before the session end time
                    time = sessionEndTime.minusSeconds(1),
                    latitude = 6.4578,
                    longitude = 0.6577,
                    horizontalAccuracy = Length.meters(2.0),
                    verticalAccuracy = Length.meters(2.0),
                    altitude = Length.meters(9.2),
                )
            )
        )
        else
        // The user doesn't allow the app to write exercise route data.
            null
    val exerciseSessionRecord = ExerciseSessionRecord(
        startTime = sessionStartTime,
        startZoneOffset = ZoneOffset.UTC,
        endTime = sessionEndTime,
        endZoneOffset = ZoneOffset.UTC,
        exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
        title = "Morning Bike Ride",
        exerciseRoute = exerciseRoute,
        metadata = Metadata.manualEntry(
            device = Device(type = Device.TYPE_PHONE)
        ),
    )
    val response = healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
}