diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 31973548dd054..f83431b58c625 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -879,6 +879,12 @@ android:name=".media.taptotransfer.sender.MediaTttSenderService" /> + + + + diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl new file mode 100644 index 0000000000000..6db06f046c551 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesProvider.aidl @@ -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 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; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl new file mode 100644 index 0000000000000..4f3e10d801f0c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesService.aidl @@ -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; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl new file mode 100644 index 0000000000000..a835f52278521 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/INearbyMediaDevicesUpdateCallback.aidl @@ -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; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl new file mode 100644 index 0000000000000..62b50ed5f953b --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.aidl @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt similarity index 87% rename from packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt index 96b853f394d4c..9cab3ab355ddb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyDevice.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/NearbyDevice.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt similarity index 50% rename from packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt rename to packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt index 3c890bc9efab8..b5eaff6e1cd9e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/RangeZone.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/media/RangeZone.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 2bc910e4a21a3..29938a05a3275 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt deleted file mode 100644 index 0453fdb459316..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/MediaNearbyDevicesManager.kt +++ /dev/null @@ -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 { - // 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. - } -} diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt new file mode 100644 index 0000000000000..eaf2bd96e37a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesService.kt @@ -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 { + 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) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt new file mode 100644 index 0000000000000..c26108619ff4f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesServiceTest.kt @@ -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 { + 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 = 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 = 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) + } +}