ServiceBackedPassiveMonitoringClient.kt

/*
 * Copyright (C) 2021 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.impl

import android.content.Context
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.health.services.client.HealthServicesException
import androidx.health.services.client.PassiveListenerCallback
import androidx.health.services.client.PassiveListenerService
import androidx.health.services.client.PassiveMonitoringClient
import androidx.health.services.client.data.PassiveListenerConfig
import androidx.health.services.client.data.PassiveMonitoringCapabilities
import androidx.health.services.client.impl.IpcConstants.PASSIVE_API_BIND_ACTION
import androidx.health.services.client.impl.IpcConstants.SERVICE_PACKAGE_NAME
import androidx.health.services.client.impl.PassiveListenerCallbackStub.PassiveListenerCallbackCache
import androidx.health.services.client.impl.internal.HsConnectionManager
import androidx.health.services.client.impl.internal.StatusCallback
import androidx.health.services.client.impl.ipc.Client
import androidx.health.services.client.impl.ipc.ClientConfiguration
import androidx.health.services.client.impl.ipc.internal.ConnectionManager
import androidx.health.services.client.impl.request.CapabilitiesRequest
import androidx.health.services.client.impl.request.FlushRequest
import androidx.health.services.client.impl.request.PassiveListenerCallbackRegistrationRequest
import androidx.health.services.client.impl.request.PassiveListenerServiceRegistrationRequest
import com.google.common.util.concurrent.FutureCallback
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import java.util.concurrent.Executor

/**
 * [PassiveMonitoringClient] implementation that is backed by Health Services.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class ServiceBackedPassiveMonitoringClient(
    private val applicationContext: Context,
    private val connectionManager: ConnectionManager =
        HsConnectionManager.getInstance(applicationContext)
) :
    PassiveMonitoringClient,
    Client<IPassiveMonitoringApiService>(
        CLIENT_CONFIGURATION,
        connectionManager,
        { binder -> IPassiveMonitoringApiService.Stub.asInterface(binder) },
        { service -> service.apiVersion }
    ) {

    private val packageName = applicationContext.packageName

    override fun setPassiveListenerServiceAsync(
        service: Class<out PassiveListenerService>,
        config: PassiveListenerConfig
    ): ListenableFuture<Void> {
        return executeWithVersionCheck(
            { remoteService, resultFuture ->
                if (config.isValidPassiveGoal()) {
                    remoteService.registerPassiveListenerService(
                        PassiveListenerServiceRegistrationRequest(
                            packageName,
                            service.name,
                            config
                        ),
                        StatusCallback(resultFuture)
                    )
                } else {
                    resultFuture.setException(HealthServicesException(
                            "DataType for the requested passive goal is not tracked"
                        ))
                }
            },
            /* minApiVersion= */ 4
        )
    }

    override fun setPassiveListenerCallback(
        config: PassiveListenerConfig,
        callback: PassiveListenerCallback
    ) {
        setPassiveListenerCallback(
            config,
            ContextCompat.getMainExecutor(applicationContext),
            callback
        )
    }

    override fun setPassiveListenerCallback(
        config: PassiveListenerConfig,
        executor: Executor,
        callback: PassiveListenerCallback
    ) {
        val callbackStub =
            PassiveListenerCallbackCache.INSTANCE.getOrCreate(packageName, executor, callback)
        val future =
            registerListener(callbackStub.listenerKey) { service, result: SettableFuture<Void?> ->
                if (config.isValidPassiveGoal()) {
                    service.registerPassiveListenerCallback(
                        PassiveListenerCallbackRegistrationRequest(packageName, config),
                        callbackStub,
                        StatusCallback(result)
                    )
                } else {
                    result.setException(
                        HealthServicesException(
                            "DataType for the requested passive goal is not tracked"
                        )
                    )
                }
            }
        Futures.addCallback(
            future,
            object : FutureCallback<Void?> {
                override fun onSuccess(result: Void?) {
                    callback.onRegistered()
                }

                override fun onFailure(t: Throwable) {
                    callback.onRegistrationFailed(t)
                }
            },
            executor
        )
    }

    override fun clearPassiveListenerServiceAsync(): ListenableFuture<Void> {
        return executeWithVersionCheck(
            { service, resultFuture ->
                service.unregisterPassiveListenerService(packageName, StatusCallback(resultFuture))
            },
            /* minApiVersion= */ 4
        )
    }

    override fun clearPassiveListenerCallbackAsync(): ListenableFuture<Void> {
        val callbackStub = PassiveListenerCallbackCache.INSTANCE.remove(packageName)
        if (callbackStub != null) {
            return unregisterListener(callbackStub.listenerKey) { service, resultFuture ->
                service.unregisterPassiveListenerCallback(packageName, StatusCallback(resultFuture))
            }
        }
        return executeWithVersionCheck(
            { service, resultFuture ->
                service.unregisterPassiveListenerCallback(packageName, StatusCallback(resultFuture))
            },
            /* minApiVersion= */ 4
        )
    }

    override fun flushAsync(): ListenableFuture<Void> {
        val request = FlushRequest(packageName)
        return execute { service, resultFuture ->
            service.flush(request, StatusCallback(resultFuture))
        }
    }

    override fun getCapabilitiesAsync(): ListenableFuture<PassiveMonitoringCapabilities> =
        Futures.transform(
            execute { service -> service.getCapabilities(CapabilitiesRequest(packageName)) },
            { response -> response!!.passiveMonitoringCapabilities },
            ContextCompat.getMainExecutor(applicationContext)
        )

    internal companion object {
        internal const val CLIENT = "HealthServicesPassiveMonitoringClient"
        internal val CLIENT_CONFIGURATION =
            ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, PASSIVE_API_BIND_ACTION)
    }
}