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)
+ }
+}