[6/X] Introduce CompanionDevicePresenceMonitor
Add CompanionDevicePresenceMonitor which uses and combines
information it receives from BleCompanionDeviceScanner and
BluetoothCompanionDeviceConnectionListener, and provides a simple
interface to CompanionDeviceManagerService.
Bug: 211398735
Test: atest CtsCompanionDeviceManagerCoreTestCases
Test: atest CtsCompanionDeviceManagerUiAutomationTestCases
Test: atest CtsOsTestCases:CompanionDeviceManagerTest
Change-Id: I9da3d34a6d2bb27f0f75dfa9341d95c37922fff2
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 0eb6b8d..ddf24da 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -35,6 +35,7 @@
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
@@ -72,7 +73,6 @@
@SuppressLint("LongLogTag")
class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 93cbe97..1ba198a 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,7 @@
package com.android.server.companion.presence;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
import android.annotation.NonNull;
@@ -39,7 +40,6 @@
class BluetoothCompanionDeviceConnectionListener
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
- private static final boolean DEBUG = false;
private static final String TAG = "CompanionDevice_PresenceMonitor_BT";
interface Callback {
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
new file mode 100644
index 0000000..6371b25
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -0,0 +1,228 @@
+/*
+ * 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.server.companion.presence;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.server.companion.AssociationStore;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
+ * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CompanionDevice_PresenceMonitor";
+
+ /** Callback for notifying about changes to status of companion devices. */
+ public interface Callback {
+ /** Invoked when companion device is found nearby or connects. */
+ void onDeviceAppeared(int associationId);
+
+ /** Invoked when a companion device no longer seen nearby or disconnects. */
+ void onDeviceDisappeared(int associationId);
+ }
+
+ private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull Callback mCallback;
+ private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ private final @NonNull BleCompanionDeviceScanner mBleScanner;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
+ private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+
+ public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
+ @NonNull Callback callback) {
+ mAssociationStore = associationStore;
+ mCallback = callback;
+
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ }
+
+ /** Initialize {@link CompanionDevicePresenceMonitor} */
+ public void init(Context context) {
+ if (DEBUG) Log.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Log.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerListener(this);
+ }
+
+ /**
+ * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+ * or devices is connected (for Bluetooth); or reported (by the application) to be
+ * nearby (for "self-managed" associations).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported");
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
+ }
+
+ @Override
+ public void onBleCompanionDeviceFound(int associationId) {
+ onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
+ }
+
+ private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
+ int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
+ if (DEBUG) {
+ Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
+ + ", source=" + sourceLoggingTag);
+ Log.d(TAG, " > association="
+ + mAssociationStore.getAssociationById(newDeviceAssociationId));
+ }
+
+ final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
+ if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
+
+ final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
+ if (DEBUG && !added) {
+ Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
+ + "present by this source (" + sourceLoggingTag + ")");
+ }
+
+ if (alreadyPresent) return;
+
+ mCallback.onDeviceAppeared(newDeviceAssociationId);
+ }
+
+ private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource,
+ int goneDeviceAssociationId, @NonNull String sourceLoggingTag) {
+ if (DEBUG) {
+ Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
+ + ", source=" + sourceLoggingTag);
+ Log.d(TAG, " > association="
+ + mAssociationStore.getAssociationById(goneDeviceAssociationId));
+ }
+
+ final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
+ if (!removed) {
+ if (DEBUG) {
+ Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+ + "as present by this source (" + sourceLoggingTag + ")");
+ }
+ return;
+ }
+
+ final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
+ if (stillPresent) {
+ if (DEBUG) Log.i(TAG, " Device is still present.");
+ return;
+ }
+
+ mCallback.onDeviceDisappeared(goneDeviceAssociationId);
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+}