HealthKitHostApiImpl.ets 5.58 KB
import { HealthAuthorization, HealthKitHostApi, Result } from '../pigeon/HealthKitApi';
import common from '@ohos.app.ability.common';
import { bundleManager, Want } from '@kit.AbilityKit';
import { productViewManager } from '@kit.AppGalleryKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { healthStore } from '@kit.HealthServiceKit';

/**
 * Bridges Flutter's health authorization flow to Health Service Kit.
 */
export class HealthKitHostApiImpl extends HealthKitHostApi {
  private static readonly HEALTH_APP_LINK = 'huaweischeme://healthapp/home/main';
  private static readonly HEALTH_APP_BUNDLE_NAME = 'com.huawei.hmos.health';
  private readonly context: common.UIAbilityContext;
  // Keep this list aligned with the health metrics requested by the Flutter app.
  private readonly authorizationRequest: healthStore.AuthorizationRequest = {
    readDataTypes: [
      healthStore.healthDataTypes.DAILY_ACTIVITIES,
      healthStore.healthDataTypes.HEART_RATE,
      healthStore.healthDataTypes.HEART_RATE_VARIABILITY,
      healthStore.healthDataTypes.BLOOD_OXYGEN_SATURATION,
      healthStore.healthDataTypes.STRESS,
      healthStore.healthDataTypes.SLEEP_RECORD,
      healthStore.healthDataTypes.BODY_TEMPERATURE,
      healthStore.healthDataTypes.SKIN_TEMPERATURE,
      healthStore.healthDataTypes.WORKOUT,
    ],
    writeDataTypes: [],
  };

  constructor(context: common.UIAbilityContext) {
    super();
    this.context = context;
  }

  getHealthServerAuthUrl(result: Result<string>): void {
    // Health Service Kit uses a system authorization sheet, not an OAuth URL.
    result.success('');
  }

  checkHealthAppAuthorization(result: Result<HealthAuthorization>): void {
    this.getAuthorization().then((response: healthStore.AuthorizationResponse) => {
      // The API returns only the data types currently granted to this app.
      // A non-empty intersection means the app can read at least one metric.
      const hasData = response.readDataTypes.length > 0;
      result.success(new HealthAuthorization(hasData ? 1 : 0, hasData));
    }).catch((error: Error) => {
      // The user still needs to accept the Huawei Health privacy agreement.
      // Return "needs authorization" so the user action can launch Health.
      console.error(`checkHealthAppAuthorization failed: ${error.name}: ${error.message}`);
      result.success(new HealthAuthorization(
        this.requiresHealthAppPrivacyAuthorization(error) ? 0 : -1,
        false,
      ));
    });
  }

  requestHealthClientAuthorization(result: Result<boolean>): void {
    this.requestAuthorizations().then((response: healthStore.AuthorizationResponse) => {
      console.error(`requestHealthClientAuthorization Huawei success`);
      result.success(response.readDataTypes.length > 0);
    }).catch((error: Error) => {
      console.error(`requestHealthClientAuthorization failed: ${error.name}: ${error.message}`);
      if (!this.requiresHealthAppPrivacyAuthorization(error)) {
        result.success(false);
        return;
      }

      // Privacy consent is managed by the Huawei Health app. Opening it does
      // not grant this application's data types yet, so Flutter must re-check
      // authorization after the user returns.
      this.openHealthAppForPrivacyAuthorization().then(() => {
        result.success(false);
      }).catch(() => {
        result.success(false);
      });
    });
  }

  override cancelHealthAppAuthorization(): boolean {
    return false;
  }


  // cancelHealthAppAuthorization(result: Result<boolean>): void {
  //   this.ensureInitialized()
  //     .then(() => healthStore.cancelAuthorizations())
  //     .then(() => result.success(true))
  //     .catch(() => result.success(false));
  // }

  private async getAuthorization(): Promise<healthStore.AuthorizationResponse> {
    await this.ensureInitialized();
    return healthStore.getAuthorizations(this.authorizationRequest);
  }

  private async requestAuthorizations(): Promise<healthStore.AuthorizationResponse> {
    await this.ensureInitialized();
    return healthStore.requestAuthorizations(this.context, this.authorizationRequest);
  }

  private async ensureInitialized(): Promise<void> {
    await healthStore.init(this.context);
  }

  private requiresHealthAppPrivacyAuthorization(error: Error): boolean {
    // Health Service Kit uses 1002703001 when the user has not accepted the
    // Huawei Health privacy agreement.
    return (error as BusinessError).code === 1002703001;
  }

  private async openHealthAppForPrivacyAuthorization(): Promise<void> {
    if (this.canOpenHealthApp()) {
      await this.openInstalledHealthApp();
      return;
    }
    this.openHealthAppInAppGallery();
  }

  private canOpenHealthApp(): boolean {
    try {
      return bundleManager.canOpenLink(HealthKitHostApiImpl.HEALTH_APP_LINK);
    } catch (error) {
      console.error(`Health app availability check failed: ${error.name}: ${error.message}`);
      return false;
    }
  }

  private async openInstalledHealthApp(): Promise<void> {
    try {
      console.error(`Opening Huawei Health`);
      await this.context.openLink(HealthKitHostApiImpl.HEALTH_APP_LINK);
    } catch (error) {
      console.error(`Failed to open Huawei Health: ${error.name}: ${error.message}`);
      this.openHealthAppInAppGallery();
    }
  }

  private openHealthAppInAppGallery(): void {
    const want: Want = {
      parameters: {
        bundleName: HealthKitHostApiImpl.HEALTH_APP_BUNDLE_NAME,
      },
    };
    try {
      productViewManager.loadProduct(this.context, want);
    } catch (error) {
      console.error(`Failed to open AppGallery recommendation: ${error.name}: ${error.message}`);
    }
  }
}