[go: nahoru, domu]

blob: b5199394c30a37af1e82b746ab7c36cd302645ca [file] [log] [blame]
/*
* Copyright 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.impl
import android.app.Application
import android.content.ComponentName
import android.content.Intent
import android.os.Looper.getMainLooper
import androidx.health.services.client.ExerciseUpdateCallback
import androidx.health.services.client.data.Availability
import androidx.health.services.client.data.DataType
import androidx.health.services.client.data.DataType.Companion.HEART_RATE_BPM
import androidx.health.services.client.data.DataType.Companion.HEART_RATE_BPM_STATS
import androidx.health.services.client.data.DataTypeAvailability.Companion.ACQUIRING
import androidx.health.services.client.data.ExerciseConfig
import androidx.health.services.client.data.ExerciseLapSummary
import androidx.health.services.client.data.ExerciseType
import androidx.health.services.client.data.ExerciseUpdate
import androidx.health.services.client.data.WarmUpConfig
import androidx.health.services.client.data.ExerciseTypeConfig
import androidx.health.services.client.data.GolfShotTrackingPlaceInfo
import androidx.health.services.client.impl.event.ExerciseUpdateListenerEvent
import androidx.health.services.client.impl.internal.IExerciseInfoCallback
import androidx.health.services.client.impl.internal.IStatusCallback
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 androidx.health.services.client.impl.response.AvailabilityResponse
import androidx.health.services.client.impl.response.ExerciseCapabilitiesResponse
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
@RunWith(RobolectricTestRunner::class)
class ServiceBackedExerciseClientTest {
private lateinit var client: ServiceBackedExerciseClient
private lateinit var fakeService: FakeServiceStub
private val callback = FakeExerciseUpdateCallback()
@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Application>()
client =
ServiceBackedExerciseClient(context, ConnectionManager(context, context.mainLooper))
fakeService = FakeServiceStub()
val packageName = ServiceBackedExerciseClient.CLIENT_CONFIGURATION.servicePackageName
val action = ServiceBackedExerciseClient.CLIENT_CONFIGURATION.bindAction
shadowOf(context).setComponentNameAndServiceForBindServiceForIntent(
Intent().setPackage(packageName).setAction(action),
ComponentName(packageName, ServiceBackedExerciseClient.CLIENT),
fakeService
)
}
@After
fun tearDown() {
client.clearUpdateCallbackAsync(callback)
}
@Test
fun registeredCallbackShouldBeInvoked() {
client.setUpdateCallback(callback)
shadowOf(getMainLooper()).idle()
assertThat(callback.onRegisteredCalls).isEqualTo(1)
assertThat(callback.onRegistrationFailedCalls).isEqualTo(0)
}
@Test
fun registrationFailedCallbackShouldBeInvoked() {
fakeService.statusCallbackAction = { it!!.onFailure("Terrible failure!") }
client.setUpdateCallback(callback)
shadowOf(getMainLooper()).idle()
assertThat(callback.onRegisteredCalls).isEqualTo(0)
assertThat(callback.onRegistrationFailedCalls).isEqualTo(1)
assertThat(callback.registrationFailureThrowables[0].message).isEqualTo("Terrible failure!")
}
@Test
fun dataTypeInAvailabilityCallbackShouldMatchRequested_justSampleType_startExercise() {
val exerciseConfig = ExerciseConfig(
ExerciseType.WALKING,
setOf(HEART_RATE_BPM),
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = false,
)
val availabilityEvent = ExerciseUpdateListenerEvent.createAvailabilityUpdateEvent(
AvailabilityResponse(HEART_RATE_BPM, ACQUIRING)
)
client.setUpdateCallback(callback)
client.startExerciseAsync(exerciseConfig)
shadowOf(getMainLooper()).idle()
fakeService.listener!!.onExerciseUpdateListenerEvent(availabilityEvent)
shadowOf(getMainLooper()).idle()
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM, ACQUIRING)
}
@Test
fun dataTypeInAvailabilityCallbackShouldMatchRequested_justStatsType_startExercise() {
val exerciseConfig = ExerciseConfig(
ExerciseType.WALKING,
setOf(HEART_RATE_BPM_STATS),
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = false
)
val availabilityEvent = ExerciseUpdateListenerEvent.createAvailabilityUpdateEvent(
// Currently the proto form of HEART_RATE_BPM and HEART_RATE_BPM_STATS is identical. The
// APK doesn't know about _STATS, so pass the sample type to mimic that behavior.
AvailabilityResponse(HEART_RATE_BPM, ACQUIRING)
)
client.setUpdateCallback(callback)
client.startExerciseAsync(exerciseConfig)
shadowOf(getMainLooper()).idle()
fakeService.listener!!.onExerciseUpdateListenerEvent(availabilityEvent)
shadowOf(getMainLooper()).idle()
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM_STATS, ACQUIRING)
}
@Test
fun dataTypeInAvailabilityCallbackShouldMatchRequested_statsAndSample_startExercise() {
val exerciseConfig = ExerciseConfig(
ExerciseType.WALKING,
setOf(HEART_RATE_BPM, HEART_RATE_BPM_STATS),
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = false
)
val availabilityEvent = ExerciseUpdateListenerEvent.createAvailabilityUpdateEvent(
// Currently the proto form of HEART_RATE_BPM and HEART_RATE_BPM_STATS is identical. The
// APK doesn't know about _STATS, so pass the sample type to mimic that behavior.
AvailabilityResponse(HEART_RATE_BPM, ACQUIRING)
)
client.setUpdateCallback(callback)
client.startExerciseAsync(exerciseConfig)
shadowOf(getMainLooper()).idle()
fakeService.listener!!.onExerciseUpdateListenerEvent(availabilityEvent)
shadowOf(getMainLooper()).idle()
// When both the sample type and stat type are requested, both should be notified
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM, ACQUIRING)
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM_STATS, ACQUIRING)
}
@Test
fun withExerciseTypeConfig_statsAndSample_startExercise() {
val exerciseConfig = ExerciseConfig(
ExerciseType.GOLF,
setOf(HEART_RATE_BPM, HEART_RATE_BPM_STATS),
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = false,
exerciseTypeConfig = ExerciseTypeConfig.createGolfExerciseTypeConfig(
GolfShotTrackingPlaceInfo.FAIRWAY
)
)
val availabilityEvent = ExerciseUpdateListenerEvent.createAvailabilityUpdateEvent(
// Currently the proto form of HEART_RATE_BPM and HEART_RATE_BPM_STATS is identical. The
// APK doesn't know about _STATS, so pass the sample type to mimic that behavior.
AvailabilityResponse(HEART_RATE_BPM, ACQUIRING)
)
client.setUpdateCallback(callback)
client.startExerciseAsync(exerciseConfig)
shadowOf(getMainLooper()).idle()
fakeService.listener!!.onExerciseUpdateListenerEvent(availabilityEvent)
shadowOf(getMainLooper()).idle()
// When both the sample type and stat type are requested, both should be notified
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM, ACQUIRING)
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM_STATS, ACQUIRING)
}
@Test
fun dataTypeInAvailabilityCallbackShouldMatchRequested_justSampleType_prepare() {
val warmUpConfig = WarmUpConfig(
ExerciseType.WALKING,
setOf(HEART_RATE_BPM),
)
val availabilityEvent = ExerciseUpdateListenerEvent.createAvailabilityUpdateEvent(
AvailabilityResponse(HEART_RATE_BPM, ACQUIRING)
)
client.setUpdateCallback(callback)
client.prepareExerciseAsync(warmUpConfig)
shadowOf(getMainLooper()).idle()
fakeService.listener!!.onExerciseUpdateListenerEvent(availabilityEvent)
shadowOf(getMainLooper()).idle()
assertThat(callback.availabilities).containsEntry(HEART_RATE_BPM, ACQUIRING)
}
class FakeExerciseUpdateCallback : ExerciseUpdateCallback {
val availabilities = mutableMapOf<DataType<*, *>, Availability>()
val registrationFailureThrowables = mutableListOf<Throwable>()
var onRegisteredCalls = 0
var onRegistrationFailedCalls = 0
override fun onRegistered() {
onRegisteredCalls++
}
override fun onRegistrationFailed(throwable: Throwable) {
onRegistrationFailedCalls++
registrationFailureThrowables.add(throwable)
}
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {}
override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {}
override fun onAvailabilityChanged(dataType: DataType<*, *>, availability: Availability) {
availabilities[dataType] = availability
}
}
class FakeServiceStub : IExerciseApiService.Stub() {
var listener: IExerciseUpdateListener? = null
var statusCallbackAction: (IStatusCallback?) -> Unit = { it!!.onSuccess() }
override fun getApiVersion(): Int = 12
override fun prepareExercise(
prepareExerciseRequest: PrepareExerciseRequest?,
statusCallback: IStatusCallback?
) {
statusCallbackAction.invoke(statusCallback)
}
override fun startExercise(
startExerciseRequest: StartExerciseRequest?,
statusCallback: IStatusCallback?
) {
statusCallbackAction.invoke(statusCallback)
}
override fun pauseExercise(packageName: String?, statusCallback: IStatusCallback?) {
throw NotImplementedError()
}
override fun resumeExercise(packageName: String?, statusCallback: IStatusCallback?) {
throw NotImplementedError()
}
override fun endExercise(packageName: String?, statusCallback: IStatusCallback?) {
throw NotImplementedError()
}
override fun markLap(packageName: String?, statusCallback: IStatusCallback?) {
throw NotImplementedError()
}
override fun getCurrentExerciseInfo(
packageName: String?,
exerciseInfoCallback: IExerciseInfoCallback?
) {
throw NotImplementedError()
}
override fun setUpdateListener(
packageName: String?,
listener: IExerciseUpdateListener?,
statusCallback: IStatusCallback?
) {
this.listener = listener
statusCallbackAction.invoke(statusCallback)
}
override fun clearUpdateListener(
packageName: String?,
listener: IExerciseUpdateListener?,
statusCallback: IStatusCallback?
) {
throw NotImplementedError()
}
override fun addGoalToActiveExercise(
request: ExerciseGoalRequest?,
statusCallback: IStatusCallback?
) {
throw NotImplementedError()
}
override fun removeGoalFromActiveExercise(
request: ExerciseGoalRequest?,
statusCallback: IStatusCallback?
) {
throw NotImplementedError()
}
override fun overrideAutoPauseAndResumeForActiveExercise(
request: AutoPauseAndResumeConfigRequest?,
statusCallback: IStatusCallback?
) {
throw NotImplementedError()
}
override fun getCapabilities(request: CapabilitiesRequest?): ExerciseCapabilitiesResponse {
throw NotImplementedError()
}
override fun flushExercise(request: FlushRequest?, statusCallback: IStatusCallback?) {
throw NotImplementedError()
}
override fun updateExerciseTypeConfigForActiveExercise(
updateExerciseTypeConfigRequest: UpdateExerciseTypeConfigRequest?,
statuscallback: IStatusCallback?
) {
throw NotImplementedError()
}
}
}