مسیرهای ورزشی را اضافه کنید

مسیرهای ورزشی به کاربران این امکان را می‌دهد که مسیر GPS را برای فعالیت‌های ورزشی مرتبط ردیابی کنند و نقشه‌های تمرینات خود را با سایر برنامه‌ها به اشتراک بگذارند.

این راهنما اطلاعاتی در مورد نحوه درخواست مجوز از کاربر ارائه می‌دهد و همچنین نحوه دریافت مجوز نوشتن داده‌های مسیر توسط برنامه‌ها را به عنوان بخشی از یک جلسه تمرینی تشریح می‌کند.

قابلیت خواندن و نوشتن برای مسیرهای تمرینی شامل موارد زیر است:

  1. برنامه‌ها یک مجوز نوشتن جدید برای مسیرهای ورزشی ایجاد می‌کنند.
  2. درج با نوشتن یک جلسه تمرینی با یک مسیر به عنوان فیلد آن اتفاق می‌افتد.
  3. خواندن:
    1. برای مالک جلسه، داده‌ها با استفاده از خواندن جلسه قابل دسترسی هستند.
    2. از یک برنامه شخص ثالث، از طریق یک کادر محاوره‌ای که به کاربر اجازه می‌دهد یک بار مسیر را بخواند.

اگر کاربر مجوزهای نوشتن نداشته باشد و مسیر تنظیم نشده باشد، مسیر به‌روزرسانی نمی‌شود.

اگر برنامه شما مجوز نوشتن مسیر داشته باشد و سعی کند با ارسال یک شیء جلسه بدون مسیر، یک جلسه را به‌روزرسانی کند، مسیر موجود حذف می‌شود.

بررسی در دسترس بودن Health Connect

قبل از تلاش برای استفاده از Health Connect، برنامه شما باید تأیید کند که Health Connect در دستگاه کاربر موجود است. Health Connect ممکن است از قبل روی همه دستگاه‌ها نصب نشده باشد یا غیرفعال باشد. می‌توانید با استفاده از متد HealthConnectClient.getSdkStatus() در دسترس بودن آن را بررسی کنید.

نحوه بررسی در دسترس بودن Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

بسته به وضعیت برگردانده شده توسط getSdkStatus() ، می‌توانید در صورت لزوم کاربر را برای نصب یا به‌روزرسانی Health Connect از فروشگاه Google Play راهنمایی کنید.

در دسترس بودن ویژگی

برای تعیین اینکه آیا دستگاه کاربر از ورزش برنامه‌ریزی‌شده در 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
}
برای کسب اطلاعات بیشتر، به «بررسی در دسترس بودن ویژگی» مراجعه کنید.

مجوزهای مورد نیاز

دسترسی به مسیر تمرین توسط مجوزهای زیر محافظت می‌شود:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
نکته: برای این نوع مجوز، READ_EXERCISE_ROUTES جمع و WRITE_EXERCISE_ROUTE مفرد است.

برای افزودن قابلیت مسیر تمرین به برنامه خود، با درخواست مجوزهای نوشتن برای نوع داده ExerciseSession شروع کنید.

برای نوشتن مسیر تمرین، باید مجوز زیر را اعلام کنید:

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

برای خواندن مسیر تمرین، باید مجوزهای زیر را درخواست کنید:

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

همچنین باید مجوز ورزش را اعلام کنید، زیرا هر مسیر با یک جلسه ورزش مرتبط است (یک جلسه = یک تمرین).

برای درخواست مجوزها، هنگام اتصال اولیه برنامه خود به Health Connect، از متد PermissionController.createRequestPermissionResultContract() استفاده کنید. چندین مجوزی که ممکن است بخواهید درخواست کنید عبارتند از:

  • داده‌های سلامت و تناسب اندام، از جمله داده‌های مسیر را بخوانید: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • داده‌های مربوط به سلامت و تناسب اندام، از جمله داده‌های مسیر را بنویسید: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • نوشتن داده‌های مسیر تمرین: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

درخواست مجوز از کاربر

پس از ایجاد یک نمونه کلاینت، برنامه شما باید از کاربر درخواست مجوز کند. کاربران باید بتوانند در هر زمانی مجوزها را اعطا یا رد کنند.

برای انجام این کار، مجموعه‌ای از مجوزها را برای انواع داده‌های مورد نیاز ایجاد کنید. مطمئن شوید که مجوزهای موجود در مجموعه ابتدا در مانیفست اندروید شما تعریف شده‌اند.

// 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)
  }
}

از آنجا که کاربران می‌توانند در هر زمانی مجوزها را اعطا یا لغو کنند، برنامه شما باید به صورت دوره‌ای مجوزهای اعطا شده را بررسی کند و سناریوهایی را که در آنها مجوز از دست می‌رود، مدیریت کند.

اطلاعات موجود در پرونده جلسه تمرینی

هر رکورد جلسه تمرینی شامل اطلاعات زیر است:

  • نوع ورزش، مثلاً دوچرخه‌سواری.
  • مسیر تمرین، که شامل اطلاعاتی مانند عرض جغرافیایی، طول جغرافیایی و ارتفاع است.

تجمیع‌های پشتیبانی‌شده

مقادیر تجمعی زیر برای ExerciseSessionRecord در دسترس هستند:

مثال استفاده

قطعه کدهای زیر نحوه خواندن و نوشتن یک مسیر تمرینی را نشان می‌دهند.

مسیر تمرین را بخوانید

برنامه شما وقتی در پس‌زمینه اجرا می‌شود، نمی‌تواند داده‌های مسیر تمرین ایجاد شده توسط برنامه‌های دیگر را بخواند.

وقتی برنامه شما در پس‌زمینه اجرا می‌شود و سعی می‌کند مسیر ورزشی ایجاد شده توسط برنامه دیگری را بخواند، Health Connect پاسخ ExerciseRouteResult.ConsentRequired را برمی‌گرداند، حتی اگر برنامه شما Always allow access to exercise route data را داشته باشد.

به همین دلیل، اکیداً توصیه می‌کنیم که درخواست مسیر را هنگام تعامل عمدی کاربر با برنامه خود، زمانی که کاربر به طور فعال با رابط کاربری برنامه شما درگیر است، انجام دهید.

برای کسب اطلاعات بیشتر در مورد خواندن‌های پس‌زمینه، به مثال خواندن پس‌زمینه مراجعه کنید.

قطعه کد زیر نحوه خواندن یک نشست در 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))
}

جلسات تمرینی

جلسات ورزشی می‌تواند شامل هر چیزی از دویدن گرفته تا بدمینتون باشد.

جلسات تمرینی بنویسید

نحوه‌ی ساخت یک درخواست درج که شامل یک جلسه (session) است، به این صورت است:

suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                title = "My Run"
            ),
            // ... other records
        )
    )
}

یک جلسه تمرین بخوانید

در اینجا مثالی از نحوه خواندن یک جلسه تمرین آورده شده است:

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                ExerciseSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (exerciseRecord in response.records) {
        // Process each exercise record
        // Optionally pull in with other data sources of the same time range.
        val distanceRecord =
            healthConnectClient
                .readRecords(
                    ReadRecordsRequest(
                        DistanceRecord::class,
                        timeRangeFilter =
                            TimeRangeFilter.between(
                                exerciseRecord.startTime,
                                exerciseRecord.endTime
                            )
                    )
                )
                .records
    }
}

نوشتن داده‌های زیرنوع

جلسات همچنین می‌توانند شامل داده‌های زیرنوع اختیاری باشند که جلسه را با اطلاعات اضافی غنی می‌کنند.

برای مثال، جلسات تمرین می‌توانند شامل کلاس‌های ExerciseSegment ، ExerciseLap و ExerciseRoute باشند:

val segments = listOf(
  ExerciseSegment(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    segmentType = ActivitySegmentType.BENCH_PRESS,
    repetitions = 373
  )
)

val laps = listOf(
  ExerciseLap(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    length = 0.meters
  )
)

ExerciseSessionRecord(
  exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
  startZoneOffset = ZoneOffset.UTC,
  endZoneOffset = ZoneOffset.UTC,
  segments = segments,
  laps = laps,
  route = route
)

حذف یک جلسه تمرینی

دو روش برای حذف یک جلسه تمرینی وجود دارد:

  1. بر اساس محدوده زمانی.
  2. توسط UID.

در اینجا نحوه حذف داده‌های زیرگروه بر اساس محدوده زمانی آورده شده است:

suspend fun deleteExerciseSessionByTimeRange(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    val timeRangeFilter = TimeRangeFilter.between(exerciseRecord.startTime, exerciseRecord.endTime)
    healthConnectClient.deleteRecords(ExerciseSessionRecord::class, timeRangeFilter)
    // delete the associated distance record
    healthConnectClient.deleteRecords(DistanceRecord::class, timeRangeFilter)
}

همچنین می‌توانید داده‌های زیرنوع را بر اساس UID حذف کنید. انجام این کار فقط جلسه تمرین را حذف می‌کند، نه داده‌های مرتبط:

suspend fun deleteExerciseSessionByUid(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    healthConnectClient.deleteRecords(
        ExerciseSessionRecord::class,
        recordIdsList = listOf(exerciseRecord.metadata.id),
        clientRecordIdsList = emptyList()
    )
}