Overview

Health Kit allows ecosystem apps to access the fitness and health data of users based on their HUAWEI IDs and authorization. For consumers, Health Kit provides a mechanism for fitness and health data storage and sharing based on flexible authorization. For developers and partners, Health Kit provides a data platform and open fitness and health capabilities, so that you can build related apps and services based on a multitude of data types. Health Kit connects hardware devices and ecosystem apps to provide consumers with health care, workout guidance, and ultimate service experience.

This codelab introduces SleepTracker. This app integrates multiple HMS Core services and runs on Android phones with HMS Core (APK) installed. SleepTracker, using the MVVM architecture, provides the sleep tracking ability and an alarm clock by visualizing data coming from wearable Huawei devices like bands and smart watches.

To use those kits, you will need to:

What You Will Need

Hardware Requirements

Software Requirements

Required Knowledge

What you will learn

In this codelab, you will learn how to:

To integrate HMS Core services, you must complete the following preparations:

For details, please refer to Preparations for Integrating HUAWEI HMS Core

You can download the sample code for SleepTracker at GitHub.

Note: You need to register as a developer to complete the operations above.

Enabling Kits and Services

  1. Sign in to AppGallery Connect, click My projects, find your project, and click your desired app. On the page that is displayed, go to Project settings > Manage APIs.
  2. Toggle on the switches for Auth Service, Account Kit, and Push Kit.

Integrating the HMS Core SDK

Huawei provides the HMS Core SDK that can be integrated by using the Maven repository. Before developing your app, you will need to integrate the HMS Core SDK into your Android Studio project.

  1. Sign in to AppGallery Connect, click My projects, find your project, and click your desired app. On the page that is displayed, go to Project settings > General information.
  2. In the App information area, click agconnect-services.json to download the configuration file.
  3. Copy the agconnect-service.json file to the app directory of your Android Studio project.
  4. Open the project-level build.gradle file.
    {buildgradle}
  5. In the build.gradle file, add the following configurations:
    {code}
  6. Open the app-level build.gradle file.
    {gradle2}
  7. In the app-level build.gradle file, add the following configurations, and click Sync Now to synchronize the configurations:<
    dependencies { implementation 'com.huawei.hms:hwid:{version}' implementation 'com.huawei.hms:push:{version}' implementation 'com.huawei.agconnect:agconnect-core:{version}' implementation 'com.huawei.agconnect:agconnect-auth:{version}' implementation 'com.huawei.hms:health:{version}' implementation 'com.huawei.hms:scan:{version}' }

Applying for Health Kit

  1. Sign in to HUAWEI Developers and click Console.
  2. Click Health Kit.
  3. Click Apply for Health Kit, agree to the agreement , and the screen for data permission application is displayed.
  4. Select the required data access permissions.

For details, please refer to Applying for Health Kit.

In this codelab, we will create a chart for different sleep types and a calendar for date selection. Also, we will display sleep details on the details page with different colors.

The app uses Account Kit to start the user authorization process. When the HUAWEI ID sign-in and authorization pages are displayed, the user can grant the corresponding data access permissions. The user can select the data types to be authorized and grant only some data permissions. The data that your app can access is within the range of data approved by the Health app and also within the scope granted by the user.
If you want to access the Health app data, enable the Health app authorization process and guide the user to complete HUAWEI ID and Health app authorization.

Add the data scopes.

Kotlin:

private fun getScopes(): MutableList { val scopeList: MutableList = LinkedList() scopeList.add(HuaweiIdAuthAPIManager.HUAWEIID_BASE_SCOPE) scopeList.add(Scope(Scopes.HEALTHKIT_SLEEP_READ)) return scopeList }

Java:

private List getScopes() { List scopeList = new LinkedList<>(); scopeList.add(HuaweiIdAuthAPIManager.HUAWEIID_BASE_SCOPE); scopeList.add(new Scope(Scopes.HEALTHKIT_SLEEP_READ)); return scopeList; }

Initialize SettingController and HuaweiIdAuthManager.

Kotlin:

private var mHuaweiIdAuthService: HuaweiIdAuthService private val mapper = HuaweiAccountMapper() init { val params = HuaweiIdAuthParamsHelper( HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM ) .setAccessToken() .setScopeList(getScopes()) .setIdToken() .setEmail() .createParams() dataController = DataControllerImpl(androidContext()).dataController, mSettingController = settingController mHuaweiIdAuthService = HuaweiIdAuthManager.getService(context, params) }

Java:

public HuaweiAccountServiceImpl(@NotNull Context context, @NotNull DataController dataController, SettingController mSettingController) { this.context = context; this.dataController = dataController; this.mSettingController = mSettingController; this.mapper = new HuaweiAccountMapper(); HuaweiIdAuthParams params = (new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)) .setAccessToken().setScopeList(this.getScopes()).setIdToken().setEmail().createParams(); this.mHuaweiIdAuthService = HuaweiIdAuthManager.getService(this.context, params); }

Check whether the app has been authorized. If not, prompt the user to perform the authorization.

Kotlin:

override fun checkOrAuthorizeHealth(intent: (Intent) -> Unit) { val healthAppSettingDataShareHealthKitActivityScheme = "huaweischeme://healthapp/achievement?module=kit" val authTask = mSettingController.healthAppAuthorization authTask.addOnSuccessListener { result -> if (Boolean.TRUE == result) { Log.i(Constants.loginActivityTAG, context.getString(R.string.check_authorize_success)) } else { val healthKitSchemaUri: Uri = Uri.parse(healthAppSettingDataShareHealthKitActivityScheme) val healtIntent = Intent(Intent.ACTION_VIEW, healthKitSchemaUri) if (healtIntent.resolveActivity(context.packageManager) != null) { intent.invoke(healtIntent) } else { Log.w( Constants.loginActivityTAG, context.getString(R.string.auth_not_resolve) ) } } }.addOnFailureListener { exception -> if (exception != null) { Log.i(Constants.loginActivityTAG, exception.message.toString()) } } }

Java:

@Override public void checkOrAuthorizeHealth(Function1 intent) { String healthAppSettingDataShareHealthKitActivityScheme = "huaweischeme://healthapp/achievement?module=kit"; Task<Boolean> authTask = mSettingController.getHealthAppAuthorization(); authTask.addOnSuccessListener(aBoolean -> { if (Boolean.TRUE.equals(aBoolean)) { Log.i(Constants.loginActivityTAG, context.getString(R.string.check_authorize_success)); } else { Uri healthKitSchemaUri = Uri.parse(healthAppSettingDataShareHealthKitActivityScheme); Intent healthIntent = new Intent(Intent.ACTION_VIEW, healthKitSchemaUri); if (healthIntent.resolveActivity(context.getPackageManager()) != null) { intent.invoke(healthIntent); } else { Log.w( Constants.loginActivityTAG, context.getString(R.string.auth_not_resolve) ); } } }); authTask.addOnFailureListener(e -> { if (e != null) { Log.i(Constants.loginActivityTAG, e.getMessage()); } }); }

Health Kit allows users to query the sleep data of the current day on the home screen, or the sleep data of any selected day. First we will develop our query function for the sleep data of the current day.

Querying the Sleep Data of the Current Day

Create a task to query the statistical data of the date type DT_CONTINUOUS_SLEEP of the current day.

Kotlin:

val todaySummaryTask = dataController.readTodaySummation(DataType.DT_CONTINUOUS_SLEEP)

Java:

Task<SampleSet> todaySummaryTask = dataController.readTodaySummation(DataType.DT_CONTINUOUS_SLEEP);

Call the relevant API for querying the statistical data of the current day. This API is used to query the statistics of a certain data type on the current day. The query time range starts from 00:00:00 of the day and ends at the system timestamp when the API is called.

Kotlin:

todaySummaryTask.addOnSuccessListener { sampleSet -> emitter.onSuccess(sampleSet) } todaySummaryTask.addOnFailureListener { exception -> if (exception.message!!.contains("50005")) { emitter.onError(exception) } }

Java:

todaySummaryTask.addOnSuccessListener(emitter::onSuccess); todaySummaryTask.addOnFailureListener(e -> { if (Objects.requireNonNull(e.getMessage()).contains("50005")) { emitter.onError(e); } });

Listen to data changes and update the UI accordingly.

Kotlin:

viewModel.readToday() private fun listenPreferencesData() { viewModel.dailyDataResultLiveData.observe(viewLifecycleOwner, Observer { when (it?.responseType) { Status.SUCCESSFUL -> { logger("Success read daily summation from HMS core") if (it.data!!.isEmpty) { setNoDataDisplay() } else { it.data?.let { sampleSet -> cleanSampleSet(sampleSet) } setDataDisplay() } logger(splitSeparator) } Status.ERROR -> { viewModel.silentSignIn() } } }) }

Java:

viewModel.getValue().readToday(); private void listenPreferencesData() { viewModel.getValue().dailyDataResultLiveData.observe(getViewLifecycleOwner(), sampleSetData -> { switch (sampleSetData.getResponseType()) { case ERROR: viewModel.getValue().silentSignIn(); case SUCCESSFUL: logger("Success read daily summation from HMS core"); assert sampleSetData.getData() != null; if (sampleSetData.getData().isEmpty()) { setNoDataDisplay(); sleepDataObj.sleepDate = Constants.defaultDate; } else { if (sampleSetData.getData() != null) { cleanSampleSet(sampleSetData.getData()); } setDataDisplay(); } logger(splitSeparator); case LOADING: Log.i(Constants.mySleepFragmentTAG, "Loading"); } }); }

Querying the Sleep Data of a Selected Day

Query the sleep data of a selected day. startTime and endTime parameters are used. In the following code, these two parameters have the same values.

Kotlin:

override fun readDailyData(startTime: Int, endTime: Int): Single<SampleSet> { return Single.create { emitter -> val dailySummationTask = dataController.readDailySummation( DataType.DT_CONTINUOUS_SLEEP, startTime, endTime) dailySummationTask.addOnSuccessListener { sampleSet -> emitter.onSuccess(sampleSet) } dailySummationTask.addOnFailureListener { exception -> if (exception.message!!.contains("50005")) { emitter.onError(exception) } } } }

Java:

@Override public Single<SampleSet> readDailyData(Integer startTime, Integer endTime) { return Single.create(emitter -> { Task<SampleSet> dailySummaryTask = dataController .readDailySummation(DataType.DT_CONTINUOUS_SLEEP, startTime, endTime); dailySummaryTask.addOnSuccessListener(emitter::onSuccess); dailySummaryTask.addOnFailureListener(e -> { if (Objects.requireNonNull(e.getMessage()).contains("50005")) { emitter.onError(e); } }); }); }

You can analyze the code in this codelab before integrating Push Kit. For more details about this kit, please refer to Push Kit Development Guide.
Use a PushManager class to obtain the access token and push token and send notifications.

Store the access token through SharedPreferences.

Kotlin:

fun getAccessToken(sleepType: String, context: Context) { AccessTokenClient.getClient().create(AccessTokenInterface::class.java) .createAccessToken( Constants.grantType, Constants.appSecret, Constants.appId ).enqueue(object : Callback<AccessToken> { override fun onFailure(call: Call<AccessToken>, t: Throwable) { Log.e(Constants.pushServiceTAG, "Error: ${t.message}") } override fun onResponse( call: Call<AccessToken>, response: Response<AccessToken> ) { if (response.isSuccessful) { Log.d( Constants.pushServiceTAG, "Access Token: ${response.body()?.accessToken}" ) val sharedPreferences = context.getSharedPreferences( Constants.packageName, Context.MODE_PRIVATE ) val sharedPushToken = sharedPreferences.getString(Constants.pushTokenStr, pushToken) accessToken = response.body()?.accessToken sharedPushToken?.let { sendNotification(it, sleepType) } } } }) }

Java:

public static void getAccessToken(String sleepType, Context context) { AccessTokenClient.getClient().create(AccessTokenInterface.class) .createAccessToken(Constants.grantType, Constants.appSecret, Constants.appId).enqueue( new Callback<AccessToken>() { @Override public void onResponse(Call<AccessToken> call, Response<AccessToken> response) { if (response.isSuccessful()) { if (response.body() != null) { SharedPreferences sharedPreferences = context.getSharedPreferences( Constants.packageName, Context.MODE_PRIVATE ); String sharedPushToken = sharedPreferences.getString(Constants.pushTokenStr, pushToken); accessToken = response.body().getAccessToken(); if (sharedPushToken != null) { sendNotification(sharedPushToken, sleepType); } } } } @Override public void onFailure(Call<AccessToken> call, Throwable t) { Log.e(Constants.pushServiceTAG, "Error: " + t.getMessage()); } } ); }

Obtain the push token.

Kotlin:

val appId: String = AGConnectServicesConfig.fromContext(context).getString("client/app_id") pushToken = HmsInstanceId.getInstance(context).getToken(appId, "HCM") if (!TextUtils.isEmpty(pushToken)) { val sharedPreferences = context.getSharedPreferences( Constants.packageName, Context.MODE_PRIVATE ) sharedPreferences.edit().putString(Constants.pushTokenStr, pushToken).apply() Log.i(Constants.pushServiceTAG, "get token: $pushToken") }

Java:

try { String appId = AGConnectServicesConfig.fromContext(context).getString("client/app_id"); pushToken = HmsInstanceId.getInstance(context).getToken(appId, "HCM"); if (!TextUtils.isEmpty(pushToken)) { SharedPreferences sharedPreferences = context.getSharedPreferences( Constants.packageName, Context.MODE_PRIVATE ); sharedPreferences.edit().putString(Constants.pushTokenStr, pushToken).apply(); Log.i(Constants.pushServiceTAG, "Get Token: " + pushToken); } } catch (Exception e) { Log.i(Constants.pushServiceTAG, "DeviceIdToken failed, " + e.getMessage()); }

Send the notification.

Kotlin:

private fun sendNotification(pushToken: String, sleepType: String) { NotificationClient.getClient().create(NotificationInterface::class.java) .createNotification( "Bearer $accessToken", selectNotificationMessageBody(sleepType, pushToken) ).enqueue(object : Callback<NotificationMessage> { override fun onResponse( call: Call<NotificationMessage>, response: Response<NotificationMessage> ) { if (response.isSuccessful) { Log.d(Constants.pushServiceTAG, "Response ${response.body()}") } } override fun onFailure(call: Call<NotificationMessage>, t: Throwable) { Log.d(Constants.pushServiceTAG, "Error: ${t.message}") } }) }

Java:

private static void sendNotification(String pushToken, String sleepType) { NotificationClient.getClient().create(NotificationInterface.class) .createNotification("Bearer " + accessToken, selectNotificationMessageBody(sleepType, pushToken)).enqueue( new Callback<NotificationMessage>() { @Override public void onResponse(Call<NotificationMessage> call, Response<NotificationMessage> response) { if (response.isSuccessful()) { Log.d(Constants.pushServiceTAG, "Response " + response.body()); } } @Override public void onFailure(Call<NotificationMessage> call, Throwable t) { Log.e(Constants.pushServiceTAG, "Error: " + t.getMessage()); } } ); }

Show notifications on the notification bar by NotificationCompat according to the sleep scenario.

Kotlin:

when (sleepType) { Constants.sleepStr -> { return NotificationMessageBody.Builder( Constants.goodNightTitle, Constants.goodNightBody[((0..4).random())], arrayOf(pushToken) ).build() } Constants.wakeStr -> { return NotificationMessageBody.Builder( Constants.goodMorningTitle, Constants.goodMorningBody[(0..5).random()], arrayOf(pushToken) ).build() } Constants.sleepReportStr -> { return NotificationMessageBody.Builder( Constants.sleepReportTitle, Constants.sleepReportBody, arrayOf(pushToken) ).build() } else -> { return NotificationMessageBody.Builder( Constants.notificationErrorTitle, Constants.notificationErrorBody, arrayOf(pushToken) ).build() } }

Java:

Random rand = new Random(); switch (sleepType) { case Constants.sleepStr: return new NotificationMessageBody.Builder( Constants.goodNightTitle, Constants.goodNightBody.get(rand.nextInt(Constants.goodNightBody.size())), Collections.singletonList(pushToken) ).build(); case Constants.wakeStr: return new NotificationMessageBody.Builder( Constants.goodMorningTitle, Constants.goodMorningBody.get(rand.nextInt(Constants.goodNightBody.size())), Collections.singletonList(pushToken) ).build(); case Constants.sleepReportStr: return new NotificationMessageBody.Builder( Constants.sleepReportTitle, Constants.sleepReportBody, Collections.singletonList(pushToken) ).build(); default: return new NotificationMessageBody.Builder( Constants.notificationErrorTitle, Constants.notificationErrorBody, Collections.singletonList(pushToken) ).build(); }

Create a NOTIFICATION_SERVICE which should be defined in AndroidManifest.xml. Obtain the access token in onStart through NOTIFICATION_SERVICE.

Kotlin:

val notification = NotificationCompat.Builder(this, Constants.CHANNEL_ID) .setContentTitle(Constants.pushTitle) .setContentText(Constants.pushBody) .build() startForeground(1001, notification) PushManager.getAccessToken(intent!!.getStringExtra(Constants.sleepTypeStr)!!, this) stopForeground(true) return super.onStartCommand(intent, flags, startId)
<service android:name=".services.PushNotificationService" />

Java:

Notification notification = new NotificationCompat.Builder(this, Constants.CHANNEL_ID) .setContentTitle(Constants.pushTitle) .setContentText(Constants.pushBody) .build(); startForeground(1001, notification); if (intent != null) { PushManager.getAccessToken(intent.getStringExtra(Constants.sleepTypeStr), this); } stopForeground(true); return super.onStartCommand(intent, flags, startId);

Create a PushService class which helps with receiving messages from NOTIFICATION_SERVICE and obtaining the push token. (Note that NOTIFICATION_SERVICE should be defined in AndroidManifest.xml first.)

Kotlin:

if (!TextUtils.isEmpty(token)) { val sharedPreferences = this.getSharedPreferences( Constants.packageName, android.content.Context.MODE_PRIVATE ) sharedPreferences.edit().putString("pushToken", token).apply() } Log.i(Constants.pushServiceTAG, "Receive Token: $token")
Log.i( Constants.pushServiceTAG, "getCollapseKey: " + message?.collapseKey + "\n getData: " + message?.data + "\n getFrom: " + message?.from + "\n getTo: " + message?.to + "\n getMessageId: " + message?.messageId + "\n getSendTime: " + message?.sentTime + "\n getMessageType: " + message?.messageType + "\n getTtl: " + message?.ttl ) val notification: RemoteMessage.Notification = message!!.notification Log.i( Constants.pushServiceTAG, "\n getImageUrl: " + notification.imageUrl + "\n getTitle: " + notification.title + "\n getTitleLocalizationKey: " + notification.titleLocalizationKey + "\n getTitleLocalizationArgs: " + Arrays.toString(notification.titleLocalizationArgs) + "\n getBody: " + notification.body + "\n getBodyLocalizationKey: " + notification.bodyLocalizationKey + "\n getBodyLocalizationArgs: " + Arrays.toString(notification.bodyLocalizationArgs) + "\n getIcon: " + notification.icon + "\n getSound: " + notification.sound + "\n getTag: " + notification.tag + "\n getColor: " + notification.color + "\n getClickAction: " + notification.clickAction + "\n getChannelId: " + notification.channelId + "\n getLink: " + notification.link + "\n getNotifyId: " + notification.notifyId )

Java:

super.onNewToken(token); if (!TextUtils.isEmpty(token)) { SharedPreferences sharedPreferences = this.getSharedPreferences( Constants.packageName, Context.MODE_PRIVATE ); sharedPreferences.edit().putString("pushToken", token).apply(); } Log.i(Constants.pushServiceTAG, "Receive Token: " + token);
super.onMessageReceived(message); if (message != null) { Log.i( Constants.pushServiceTAG, "getCollapseKey: " + message.getCollapseKey() + "\n getData: " + message.getData() + "\n getFrom: " + message.getFrom() + "\n getTo: " + message.getTo() + "\n getMessageId: " + message.getMessageId() + "\n getSendTime: " + message.getSentTime() + "\n getMessageType: " + message.getMessageType() + "\n getTtl: " + message.getTtl()); }

In this codelab, QR code is scanned to stop alarm clocks. Enable QR code scanning in AlarmScanActivity. To obtain the QR Code, use RemoteView in the FrameLayout class of Scan Kit.

Kotlin:

val dm = resources.displayMetrics val density = dm.density mScreenWidth = resources.displayMetrics.widthPixels mScreenHeight = resources.displayMetrics.heightPixels val scanFrameSize = (scanFrameSize * density) val rect = Rect() rect.left = (mScreenWidth / 2 - scanFrameSize / 2).toInt() rect.right = (mScreenWidth / 2 + scanFrameSize / 2).toInt() rect.top = (mScreenHeight / 2 - scanFrameSize / 2).toInt() rect.bottom = (mScreenHeight / 2 + scanFrameSize / 2).toInt() remoteView = RemoteView.Builder().setContext(this).setBoundingBox(rect) .setFormat(HmsScan.QRCODE_SCAN_TYPE).build() remoteView?.onCreate(savedInstanceState) remoteView?.setOnResultCallback { result -> //judge the result is effective println("QR CODE : " + result[0].getOriginalValue()) if (result != null && result.isNotEmpty() && result[0] != null && !TextUtils.isEmpty( result[0].getOriginalValue() ) && result[0].originalValue == Constants.alarmOffQRResult ) { val intentService = Intent(this, AlarmClock::class.java) stopService(intentService) finish() } } val params = FrameLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT ) rim.addView(remoteView, params)

Java:

DisplayMetrics dm = getResources().getDisplayMetrics(); float density = dm.density; mScreenWidth = dm.widthPixels; mScreenHeight = dm.heightPixels; float scanFrameSize = (this.scanFrameSize * density); Rect rect = new Rect(); rect.left = (int) (((double)mScreenWidth / 2) - (scanFrameSize / 2)); rect.left = (int) (((double)mScreenWidth / 2) - (scanFrameSize / 2)); rect.right = (int) ((double)mScreenWidth / 2 + scanFrameSize / 2); rect.top = (int) ((double)mScreenHeight / 2 - scanFrameSize / 2); rect.bottom = (int) ((double)mScreenHeight / 2 + scanFrameSize / 2); remoteView = new RemoteView.Builder().setContext(this).setBoundingBox(rect) .setFormat(HmsScan.QRCODE_SCAN_TYPE).build(); remoteView.onCreate(savedInstanceState); remoteView.setOnResultCallback(result -> { System.out.println("QR Code: " + result[0].getOriginalValue()); if (result[0] != null && !TextUtils.isEmpty( result[0].getOriginalValue() ) && result[0].originalValue.equals(Constants.alarmOffQRResult) ) { Intent intentService = new Intent(this, AlarmClock.class); stopService(intentService); finish(); } }); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); FrameLayout rim = findViewById(R.id.rim); rim.addView(remoteView, params);

Well done. You have successfully completed this codelab and learned how to:

For more information, please click the following links:

Health Kit

Scan Kit

Account Kit

Auth Service

Push Kit

You can download the incompleted sample code for SleepTracker at Github

Download
Code copied