Merge "[Output Switcher] Implement nearby device callbacks."

This commit is contained in:
Caitlin Cassidy
2022-02-02 14:37:20 +00:00
committed by Android (Google) Code Review
11 changed files with 368 additions and 77 deletions

View File

@@ -879,6 +879,12 @@
android:name=".media.taptotransfer.sender.MediaTttSenderService"
/>
<!-- Service for external clients to notify us of nearby media devices -->
<!-- TODO(b/216313420): Export and guard with a permission. -->
<service
android:name=".media.nearby.NearbyMediaDevicesService"
/>
<receiver
android:name=".tuner.TunerService$ClearReceiver"
android:exported="false">

View File

@@ -0,0 +1,43 @@
/*
* 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 com.android.systemui.shared.media;
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback;
import com.android.systemui.shared.media.NearbyDevice;
/**
* An interface that provides information about nearby devices that are able to play media.
*
* External clients will implement this interface and System UI will invoke it if it's passed to
* SystemUI via {@link INearbyMediaDevicesService.registerProvider}.
*/
interface INearbyMediaDevicesProvider {
/**
* Returns a list of nearby devices that are able to play media.
*/
List<NearbyDevice> getCurrentNearbyDevices() = 1;
/**
* Registers a callback that will be notified each time the status of a nearby device changes.
*/
oneway void registerNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 2;
/**
* Unregisters a callback. See {@link registerNearbyDevicesCallback}.
*/
oneway void unregisterNearbyDevicesCallback(in INearbyMediaDevicesUpdateCallback callback) = 3;
}

View File

@@ -0,0 +1,33 @@
/*
* 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 com.android.systemui.shared.media;
import com.android.systemui.shared.media.INearbyMediaDevicesProvider;
/**
* An interface that can be invoked to notify System UI of nearby media devices.
*
* External clients wanting to notify System UI about the status of nearby media devices should
* implement {@link INearbyMediaDevicesProvider} and then register it with system UI using this
* service.
*
* System UI will implement this interface and external clients will invoke it.
*/
interface INearbyMediaDevicesService {
/** Registers a new provider. */
oneway void registerProvider(INearbyMediaDevicesProvider provider) = 1;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 com.android.systemui.shared.media;
/**
* A callback used to notify implementors of changes in the status of nearby devices that are able
* to play media.
*
* External clients may allow registration of these callbacks and external clients will be
* responsible for notifying the callbacks appropriately. System UI is only a mediator between the
* external client and these callbacks.
*/
interface INearbyMediaDevicesUpdateCallback {
/** Unknown distance range. */
const int RANGE_UNKNOWN = 0;
/** Distance is very far away from the peer device. */
const int RANGE_FAR = 1;
/** Distance is relatively long from the peer device, typically a few meters. */
const int RANGE_LONG = 2;
/** Distance is close to the peer device, typically with one or two meter. */
const int RANGE_CLOSE = 3;
/** Distance is very close to the peer device, typically within one meter or less. */
const int RANGE_WITHIN_REACH = 4;
/** Invoked by external clients when media device changes are detected. */
oneway void nearbyDeviceUpdate(in String routeId, in int rangeZone) = 1;
}

View File

@@ -0,0 +1,19 @@
/*
* 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 com.android.systemui.shared.media;
parcelable NearbyDevice;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.media.nearby
package com.android.systemui.shared.media
import android.os.Parcel
import android.os.Parcelable
@@ -26,14 +26,15 @@ import android.os.Parcelable
* - [routeId] identifying the media route
* - [rangeZone] specifying how far away the device with the media route is from this device.
*/
class NearbyDevice(parcel: Parcel) : Parcelable {
var routeId: String? = null
class NearbyDevice(
val routeId: String?,
@RangeZone val rangeZone: Int
) : Parcelable {
init {
routeId = parcel.readString() ?: "unknown"
private constructor(parcel: Parcel) : this(
routeId = parcel.readString() ?: null,
rangeZone = parcel.readInt()
}
)
override fun describeContents() = 0

View File

@@ -14,31 +14,18 @@
* limitations under the License.
*/
package com.android.systemui.media.nearby
package com.android.systemui.shared.media
import androidx.annotation.IntDef
import kotlin.annotation.AnnotationRetention
@IntDef(
RangeZone.RANGE_UNKNOWN,
RangeZone.RANGE_FAR,
RangeZone.RANGE_LONG,
RangeZone.RANGE_CLOSE,
RangeZone.RANGE_WITHIN_REACH
INearbyMediaDevicesUpdateCallback.RANGE_UNKNOWN,
INearbyMediaDevicesUpdateCallback.RANGE_FAR,
INearbyMediaDevicesUpdateCallback.RANGE_LONG,
INearbyMediaDevicesUpdateCallback.RANGE_CLOSE,
INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
)
@Retention(AnnotationRetention.SOURCE)
/** The various range zones a device can be in, in relation to the current device. */
annotation class RangeZone {
companion object {
/** Unknown distance range. */
const val RANGE_UNKNOWN = 0
/** Distance is very far away from the peer device. */
const val RANGE_FAR = 1
/** Distance is relatively long from the peer device, typically a few meters. */
const val RANGE_LONG = 2
/** Distance is close to the peer device, typically with one or two meter. */
const val RANGE_CLOSE = 3
/** Distance is very close to the peer device, typically within one meter or less. */
const val RANGE_WITHIN_REACH = 4
}
}
annotation class RangeZone

View File

@@ -26,6 +26,7 @@ import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
import com.android.systemui.media.nearby.NearbyMediaDevicesService;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
@@ -142,4 +143,10 @@ public interface MediaModule {
@IntoMap
@ClassKey(MediaTttSenderService.class)
Service bindMediaTttSenderService(MediaTttSenderService service);
/** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap
@ClassKey(NearbyMediaDevicesService.class)
Service bindMediaNearbyDevicesService(NearbyMediaDevicesService service);
}

View File

@@ -1,51 +0,0 @@
/*
* 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 com.android.systemui.media.nearby
import com.android.systemui.dagger.SysUISingleton
/**
* A manager that returns information about devices that are nearby and can receive media transfers.
*/
@SysUISingleton
class MediaNearbyDevicesManager {
/** Returns a list containing the current nearby devices. */
fun getCurrentNearbyDevices(): List<NearbyDevice> {
// TODO(b/216313420): Implement this function.
return emptyList()
}
/**
* Registers [callback] to be notified each time a device's range changes or when a new device
* comes within range.
*/
fun registerNearbyDevicesCallback(
callback: (device: NearbyDevice) -> Unit
) {
// TODO(b/216313420): Implement this function.
}
/**
* Un-registers [callback]. See [registerNearbyDevicesCallback].
*/
fun unregisterNearbyDevicesCallback(
callback: (device: NearbyDevice) -> Unit
) {
// TODO(b/216313420): Implement this function.
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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 com.android.systemui.media.nearby
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.media.INearbyMediaDevicesProvider
import com.android.systemui.shared.media.INearbyMediaDevicesService
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
import com.android.systemui.shared.media.NearbyDevice
import javax.inject.Inject
/**
* A service that acts as a bridge between (1) external clients that have data on nearby devices
* that are able to play media and (2) internal clients (like media Output Switcher) that need data
* on these nearby devices.
*
* TODO(b/216313420): Add logging to this class.
*/
@SysUISingleton
class NearbyMediaDevicesService @Inject constructor() : Service() {
private var provider: INearbyMediaDevicesProvider? = null
private val binder: IBinder = object : INearbyMediaDevicesService.Stub() {
override fun registerProvider(newProvider: INearbyMediaDevicesProvider) {
provider = newProvider
newProvider.asBinder().linkToDeath(
{
// We might've gotten a new provider before the old provider died, so we only
// need to clear our provider if the most recent provider died.
if (provider == newProvider) {
provider = null
}
},
/* flags= */ 0
)
}
}
override fun onBind(intent: Intent?): IBinder = binder
/** Returns a list containing the current nearby devices. */
fun getCurrentNearbyDevices(): List<NearbyDevice> {
val currentProvider = provider ?: return emptyList()
return currentProvider.currentNearbyDevices
}
/**
* Registers [callback] to be notified each time a device's range changes or when a new device
* comes within range.
*/
fun registerNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
val currentProvider = provider ?: return
currentProvider.registerNearbyDevicesCallback(callback)
}
/**
* Un-registers [callback]. See [registerNearbyDevicesCallback].
*/
fun unregisterNearbyDevicesCallback(callback: INearbyMediaDevicesUpdateCallback) {
val currentProvider = provider ?: return
currentProvider.unregisterNearbyDevicesCallback(callback)
}
}

View File

@@ -0,0 +1,124 @@
package com.android.systemui.media.nearby
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.media.INearbyMediaDevicesProvider
import com.android.systemui.shared.media.INearbyMediaDevicesService
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_LONG
import com.android.systemui.shared.media.INearbyMediaDevicesUpdateCallback.RANGE_WITHIN_REACH
import com.android.systemui.shared.media.NearbyDevice
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@SmallTest
class NearbyMediaDevicesServiceTest : SysuiTestCase() {
private lateinit var service: NearbyMediaDevicesService
private lateinit var binderInterface: INearbyMediaDevicesService
@Before
fun setUp() {
service = NearbyMediaDevicesService()
binderInterface = INearbyMediaDevicesService.Stub.asInterface(service.onBind(null))
}
@Test
fun getCurrentNearbyDevices_noProviderRegistered_returnsEmptyList() {
assertThat(service.getCurrentNearbyDevices()).isEmpty()
}
@Test
fun getCurrentNearbyDevices_providerRegistered_returnsProviderInfo() {
val nearbyDevice1 = NearbyDevice("routeId1", RANGE_LONG)
val nearbyDevice2 = NearbyDevice("routeId2", RANGE_WITHIN_REACH)
val provider = object : INearbyMediaDevicesProvider.Stub() {
override fun getCurrentNearbyDevices(): List<NearbyDevice> {
return listOf(nearbyDevice1, nearbyDevice2)
}
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
}
binderInterface.registerProvider(provider)
val returnedNearbyDevices = service.getCurrentNearbyDevices()
assertThat(returnedNearbyDevices).isEqualTo(listOf(nearbyDevice1, nearbyDevice2))
}
@Test
fun registerNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
service.registerNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
})
}
@Test
fun registerNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = object : INearbyMediaDevicesProvider.Stub() {
var registeredCallback: INearbyMediaDevicesUpdateCallback? = null
override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {
registeredCallback = callback
}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
}
binderInterface.registerProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
}
service.registerNearbyDevicesCallback(callback)
assertThat(provider.registeredCallback).isEqualTo(callback)
}
@Test
fun unregisterNearbyDevicesCallback_noProviderRegistered_noCrash() {
// No assert, just needs no crash
service.unregisterNearbyDevicesCallback(object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
})
}
@Test
fun unregisterNearbyDevicesCallback_providerRegistered_providerReceivesCallback() {
val provider = object : INearbyMediaDevicesProvider.Stub() {
var unregisteredCallback: INearbyMediaDevicesUpdateCallback? = null
override fun getCurrentNearbyDevices(): List<NearbyDevice> = listOf()
override fun registerNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {}
override fun unregisterNearbyDevicesCallback(
callback: INearbyMediaDevicesUpdateCallback?
) {
unregisteredCallback = callback
}
}
binderInterface.registerProvider(provider)
val callback = object : INearbyMediaDevicesUpdateCallback.Stub() {
override fun nearbyDeviceUpdate(routeId: String?, rangeZone: Int) {}
}
service.unregisterNearbyDevicesCallback(callback)
assertThat(provider.unregisteredCallback).isEqualTo(callback)
}
}