PassiveListenerService.kt

/*
 * Copyright (C) 2022 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
 *
 *      http://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 androidx.health.services.client

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.health.services.client.data.DataPoint
import androidx.health.services.client.data.DataPointContainer
import androidx.health.services.client.data.HealthEvent
import androidx.health.services.client.data.PassiveGoal
import androidx.health.services.client.data.UserActivityInfo
import androidx.health.services.client.impl.IPassiveListenerService
import androidx.health.services.client.impl.event.PassiveListenerEvent
import androidx.health.services.client.impl.response.HealthEventResponse
import androidx.health.services.client.impl.response.PassiveMonitoringGoalResponse
import androidx.health.services.client.impl.response.PassiveMonitoringUpdateResponse
import androidx.health.services.client.proto.EventsProto.PassiveListenerEvent.EventCase.EVENT_NOT_SET
import androidx.health.services.client.proto.EventsProto.PassiveListenerEvent.EventCase.HEALTH_EVENT_RESPONSE
import androidx.health.services.client.proto.EventsProto.PassiveListenerEvent.EventCase.PASSIVE_GOAL_RESPONSE
import androidx.health.services.client.proto.EventsProto.PassiveListenerEvent.EventCase.PASSIVE_UPDATE_RESPONSE
import androidx.health.services.client.proto.EventsProto.PassiveListenerEvent.EventCase.PERMISSION_LOST_RESPONSE

/**
 * Service that enables receiving passive monitoring updates throughout the day when the app may not
 * be running.
 *
 * Health Services will bind to the [PassiveListenerService] to deliver passive monitoring updates
 * such as data or goal updates. Clients should extend this service and override those methods of
 * the [PassiveListenerCallback] that they care about. They can then pass in their service to
 * [PassiveMonitoringClient.setPassiveListenerServiceAsync] to receive data updates.
 */
@Suppress("UNUSED_PARAMETER")
public abstract class PassiveListenerService : Service() {

    private var wrapper: IPassiveListenerServiceWrapper? = null

    final override fun onBind(intent: Intent): IBinder? {
        wrapper = IPassiveListenerServiceWrapper()
        return wrapper
    }

    /**
     * Called when new [DataPoint]s are generated.
     *
     * @param dataPoints a list of new [DataPoint]s generated
     */
    public open fun onNewDataPointsReceived(dataPoints: DataPointContainer) {}

    /**
     * Called when new [UserActivityInfo] is generated.
     *
     * @param info a new [UserActivityInfo] representing the current state
     */
    public open fun onUserActivityInfoReceived(info: UserActivityInfo) {}

    /**
     * Called when a [PassiveGoal] has been completed.
     *
     * @param goal the [PassiveGoal] that has been completed
     */
    public open fun onGoalCompleted(goal: PassiveGoal) {}

    /**
     * Called when a [HealthEvent] has been detected.
     *
     * @param event the [HealthEvent] that has been detected
     */
    public open fun onHealthEventReceived(event: HealthEvent) {}

    /**
     * Called when the client has lost permission for the passive listener request. If this happens,
     * WHS will automatically unregister the client request and stop the relevant sensors. The
     * client can use this callback to detect the problem and either prompt the user to re-grant the
     * permissions or re-register while requesting only that which the app does have permission for.
     */
    public open fun onPermissionLost() {}

    internal inner class IPassiveListenerServiceWrapper : IPassiveListenerService.Stub() {

        override fun onPassiveListenerEvent(event: PassiveListenerEvent) {
            val proto = event.proto

            when (proto.eventCase) {
                PASSIVE_UPDATE_RESPONSE -> {
                    val response = PassiveMonitoringUpdateResponse(proto.passiveUpdateResponse)
                    if (!response.passiveMonitoringUpdate.dataPoints.dataPoints.isEmpty()) {
                        this@PassiveListenerService.onNewDataPointsReceived(
                            response.passiveMonitoringUpdate.dataPoints
                        )
                    }
                    for (userActivityInfo in
                        response.passiveMonitoringUpdate.userActivityInfoUpdates) {
                        this@PassiveListenerService.onUserActivityInfoReceived(userActivityInfo)
                    }
                }
                PASSIVE_GOAL_RESPONSE -> {
                    val response = PassiveMonitoringGoalResponse(proto.passiveGoalResponse)
                    this@PassiveListenerService.onGoalCompleted(response.passiveGoal)
                }
                HEALTH_EVENT_RESPONSE -> {
                    val response = HealthEventResponse(proto.healthEventResponse)
                    this@PassiveListenerService.onHealthEventReceived(response.healthEvent)
                }
                PERMISSION_LOST_RESPONSE -> {
                    this@PassiveListenerService.onPermissionLost()
                }
                null, EVENT_NOT_SET -> Log.w(TAG, "Received unknown event ${proto.eventCase}")
            }
        }

        override fun getApiVersion(): Int {
            return API_VERSION
        }
    }

    private companion object {
        private const val TAG = "PassiveListenerService"
    }
}