| /* |
| * 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.GuardedBy |
| import androidx.annotation.RestrictTo |
| import androidx.core.content.ContextCompat |
| import androidx.health.services.client.ExerciseClient |
| import androidx.health.services.client.ExerciseUpdateCallback |
| import androidx.health.services.client.data.DataType |
| import androidx.health.services.client.data.ExerciseCapabilities |
| import androidx.health.services.client.data.ExerciseConfig |
| import androidx.health.services.client.data.ExerciseGoal |
| import androidx.health.services.client.data.ExerciseInfo |
| import androidx.health.services.client.data.ExerciseTypeConfig |
| import androidx.health.services.client.data.WarmUpConfig |
| import androidx.health.services.client.impl.IpcConstants.EXERCISE_API_BIND_ACTION |
| import androidx.health.services.client.impl.IpcConstants.SERVICE_PACKAGE_NAME |
| import androidx.health.services.client.impl.internal.ExerciseInfoCallback |
| 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.AutoPauseAndResumeConfigRequest |
| import androidx.health.services.client.impl.request.CapabilitiesRequest |
| import androidx.health.services.client.impl.request.ExerciseGoalRequest |
| import androidx.health.services.client.impl.request.FlushRequest |
| import androidx.health.services.client.impl.request.PrepareExerciseRequest |
| import androidx.health.services.client.impl.request.StartExerciseRequest |
| import androidx.health.services.client.impl.request.UpdateExerciseTypeConfigRequest |
| 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 |
| |
| /** |
| * [ExerciseClient] implementation that is backed by Health Services. |
| * |
| * @hide |
| */ |
| @RestrictTo(RestrictTo.Scope.LIBRARY) |
| internal class ServiceBackedExerciseClient( |
| private val context: Context, |
| connectionManager: ConnectionManager = HsConnectionManager.getInstance(context) |
| ) : |
| ExerciseClient, |
| Client<IExerciseApiService>( |
| CLIENT_CONFIGURATION, |
| connectionManager, |
| { binder -> IExerciseApiService.Stub.asInterface(binder) }, |
| { service -> service.apiVersion } |
| ) { |
| |
| private val requestedDataTypesLock = Any() |
| @GuardedBy("requestedDataTypesLock") |
| private val requestedDataTypes: MutableSet<DataType<*, *>> = mutableSetOf() |
| private val packageName = context.packageName |
| |
| override fun prepareExerciseAsync(configuration: WarmUpConfig): ListenableFuture<Void> = |
| execute { service, resultFuture -> |
| service.prepareExercise( |
| PrepareExerciseRequest(packageName, configuration), |
| object : StatusCallback(resultFuture) { |
| override fun onSuccess() { |
| synchronized(requestedDataTypesLock) { |
| requestedDataTypes.clear() |
| requestedDataTypes.addAll(configuration.dataTypes) |
| } |
| super.onSuccess() |
| } |
| } |
| ) |
| } |
| |
| override fun startExerciseAsync(configuration: ExerciseConfig): ListenableFuture<Void> = |
| execute { service, resultFuture -> |
| service.startExercise( |
| StartExerciseRequest(packageName, configuration), |
| object : StatusCallback(resultFuture) { |
| override fun onSuccess() { |
| synchronized(requestedDataTypesLock) { |
| requestedDataTypes.clear() |
| requestedDataTypes.addAll(configuration.dataTypes) |
| } |
| super.onSuccess() |
| } |
| } |
| ) |
| } |
| |
| override fun pauseExerciseAsync(): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.pauseExercise(packageName, StatusCallback(resultFuture)) |
| } |
| |
| override fun resumeExerciseAsync(): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.resumeExercise(packageName, StatusCallback(resultFuture)) |
| } |
| |
| override fun endExerciseAsync(): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.endExercise(packageName, StatusCallback(resultFuture)) |
| } |
| |
| override fun flushAsync(): ListenableFuture<Void> { |
| val request = FlushRequest(packageName) |
| return execute { service, resultFuture -> |
| service.flushExercise(request, StatusCallback(resultFuture)) |
| } |
| } |
| |
| override fun markLapAsync(): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.markLap(packageName, StatusCallback(resultFuture)) |
| } |
| |
| override fun getCurrentExerciseInfoAsync(): ListenableFuture<ExerciseInfo> { |
| return execute { service, resultFuture -> |
| service.getCurrentExerciseInfo(packageName, ExerciseInfoCallback(resultFuture)) |
| } |
| } |
| |
| override fun setUpdateCallback(callback: ExerciseUpdateCallback) { |
| setUpdateCallback(ContextCompat.getMainExecutor(context), callback) |
| } |
| |
| override fun setUpdateCallback( |
| executor: Executor, |
| callback: ExerciseUpdateCallback |
| ) { |
| val listenerStub = |
| ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.getOrCreate( |
| callback, |
| executor, |
| requestedDataTypesProvider = { |
| synchronized(requestedDataTypesLock) { |
| requestedDataTypes |
| } |
| } |
| ) |
| val future = |
| registerListener(listenerStub.listenerKey) { service, result: SettableFuture<Void?> -> |
| service.setUpdateListener(packageName, listenerStub, StatusCallback(result)) |
| } |
| Futures.addCallback( |
| future, |
| object : FutureCallback<Void?> { |
| override fun onSuccess(result: Void?) { |
| callback.onRegistered() |
| } |
| |
| override fun onFailure(t: Throwable) { |
| callback.onRegistrationFailed(t) |
| } |
| }, |
| executor) |
| } |
| |
| override fun clearUpdateCallbackAsync( |
| callback: ExerciseUpdateCallback |
| ): ListenableFuture<Void> { |
| val listenerStub = |
| ExerciseUpdateListenerStub.ExerciseUpdateListenerCache.INSTANCE.remove(callback) |
| ?: return Futures.immediateFailedFuture( |
| IllegalArgumentException("Given listener was not added.") |
| ) |
| return unregisterListener(listenerStub.listenerKey) { service, resultFuture -> |
| service.clearUpdateListener(packageName, listenerStub, StatusCallback(resultFuture)) |
| } |
| } |
| |
| override fun addGoalToActiveExerciseAsync( |
| exerciseGoal: ExerciseGoal<*> |
| ): ListenableFuture<Void> = |
| execute { service, resultFuture -> |
| service.addGoalToActiveExercise( |
| ExerciseGoalRequest(packageName, exerciseGoal), |
| StatusCallback(resultFuture) |
| ) |
| } |
| |
| override fun removeGoalFromActiveExerciseAsync( |
| exerciseGoal: ExerciseGoal<*> |
| ): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.removeGoalFromActiveExercise( |
| ExerciseGoalRequest(packageName, exerciseGoal), |
| StatusCallback(resultFuture) |
| ) |
| } |
| |
| override fun overrideAutoPauseAndResumeForActiveExerciseAsync( |
| enabled: Boolean |
| ): ListenableFuture<Void> = execute { service, resultFuture -> |
| service.overrideAutoPauseAndResumeForActiveExercise( |
| AutoPauseAndResumeConfigRequest(packageName, enabled), |
| StatusCallback(resultFuture) |
| ) |
| } |
| |
| override fun getCapabilitiesAsync(): ListenableFuture<ExerciseCapabilities> = |
| Futures.transform( |
| execute { service -> service.getCapabilities(CapabilitiesRequest(packageName)) }, |
| { response -> response!!.exerciseCapabilities }, |
| ContextCompat.getMainExecutor(context) |
| ) |
| |
| override fun updateExerciseTypeConfigAsync( |
| exerciseTypeConfig: ExerciseTypeConfig |
| ): ListenableFuture<Void> { |
| return executeWithVersionCheck( |
| { service, resultFuture -> |
| service.updateExerciseTypeConfigForActiveExercise( |
| UpdateExerciseTypeConfigRequest(packageName, exerciseTypeConfig), |
| StatusCallback(resultFuture) |
| ) |
| }, |
| 3 |
| ) |
| } |
| |
| internal companion object { |
| internal const val CLIENT = "HealthServicesExerciseClient" |
| internal val CLIENT_CONFIGURATION = |
| ClientConfiguration(CLIENT, SERVICE_PACKAGE_NAME, EXERCISE_API_BIND_ACTION) |
| } |
| } |