[5/X] Introduce BleCompanionDeviceScanner
Bug: 211398735 Test: atest CtsCompanionDeviceManagerCoreTestCases Test: atest CtsCompanionDeviceManagerUiAutomationTestCases Test: atest CtsOsTestCases:CompanionDeviceManagerTest Change-Id: I798d788bb27d6c4c103a1eeb9405b3d78c689ba3
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothAdapter.nameForState;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
|
||||
import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY;
|
||||
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
|
||||
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
|
||||
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.Utils.btDeviceToString;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.MainThread;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanCallback;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.companion.AssociationInfo;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.companion.AssociationStore;
|
||||
import com.android.server.companion.AssociationStore.ChangeType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
|
||||
|
||||
/**
|
||||
* When using {@link ScanSettings#SCAN_MODE_LOW_POWER}, it usually takes from 20 seconds to
|
||||
* 2 minutes for the BLE scanner to find advertisements sent from the same device.
|
||||
* On the other hand, {@link android.bluetooth.BluetoothAdapter.LeScanCallback} will report
|
||||
* {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST} 10 sec after it finds the
|
||||
* advertisement for the first time (add reports
|
||||
* {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH FIRST_MATCH}).
|
||||
* To avoid constantly reporting {@link Callback#onBleCompanionDeviceFound(int) onDeviceFound()}
|
||||
* and {@link Callback#onBleCompanionDeviceLost(int) onDeviceLost()} (while the device is
|
||||
* actually present) to its clients, {@link BleCompanionDeviceScanner}, will add 1-minute delay
|
||||
* after it receives {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST}.
|
||||
*/
|
||||
private static final int NOTIFY_DEVICE_LOST_DELAY = 2 * 60 * 1000; // 2 Min.
|
||||
|
||||
interface Callback {
|
||||
void onBleCompanionDeviceFound(int associationId);
|
||||
|
||||
void onBleCompanionDeviceLost(int associationId);
|
||||
}
|
||||
|
||||
private final @NonNull AssociationStore mAssociationStore;
|
||||
private final @NonNull Callback mCallback;
|
||||
private final @NonNull MainThreadHandler mMainThreadHandler;
|
||||
|
||||
// Non-null after init().
|
||||
private @Nullable BluetoothAdapter mBtAdapter;
|
||||
// Non-null after init() and when BLE is available. Otherwise - null.
|
||||
private @Nullable BluetoothLeScanner mBleScanner;
|
||||
// Only accessed from the Main thread.
|
||||
private boolean mScanning = false;
|
||||
|
||||
BleCompanionDeviceScanner(
|
||||
@NonNull AssociationStore associationStore, @NonNull Callback callback) {
|
||||
mAssociationStore = associationStore;
|
||||
mCallback = callback;
|
||||
mMainThreadHandler = new MainThreadHandler();
|
||||
}
|
||||
|
||||
@MainThread
|
||||
void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) {
|
||||
if (DEBUG) Log.i(TAG, "init()");
|
||||
|
||||
if (mBtAdapter != null) {
|
||||
throw new IllegalStateException(getClass().getSimpleName() + " is already initialized");
|
||||
}
|
||||
mBtAdapter = requireNonNull(btAdapter);
|
||||
|
||||
checkBleState();
|
||||
registerBluetoothStateBroadcastReceiver(context);
|
||||
|
||||
mAssociationStore.registerListener(this);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
final void restartScan() {
|
||||
enforceInitialized();
|
||||
|
||||
if (DEBUG) Log.i(TAG , "restartScan()");
|
||||
if (mBleScanner == null) {
|
||||
if (DEBUG) Log.d(TAG, " > BLE is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
stopScanIfNeeded();
|
||||
startScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssociationChanged(@ChangeType int changeType, AssociationInfo association) {
|
||||
// Simply restart scanning.
|
||||
if (Looper.getMainLooper().isCurrentThread()) {
|
||||
restartScan();
|
||||
} else {
|
||||
mMainThreadHandler.post(this::restartScan);
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void checkBleState() {
|
||||
enforceInitialized();
|
||||
|
||||
final boolean bleAvailable = isBleAvailable();
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
|
||||
}
|
||||
if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) {
|
||||
// Nothing changed.
|
||||
if (DEBUG) Log.i(TAG, " > BLE status did not change");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bleAvailable) {
|
||||
mBleScanner = mBtAdapter.getBluetoothLeScanner();
|
||||
if (mBleScanner == null) {
|
||||
// Oops, that's a race condition. Can return.
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, " > BLE is now available");
|
||||
|
||||
startScan();
|
||||
} else {
|
||||
if (DEBUG) Log.i(TAG, " > BLE is now unavailable");
|
||||
|
||||
stopScanIfNeeded();
|
||||
mBleScanner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
|
||||
* access level, so it's not accessible from here.
|
||||
*/
|
||||
private boolean isBleAvailable() {
|
||||
final int state = mBtAdapter.getLeState();
|
||||
if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
|
||||
return state == STATE_ON || state == STATE_BLE_ON;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void startScan() {
|
||||
enforceInitialized();
|
||||
// This method should not be called if scan is already in progress.
|
||||
if (mScanning) throw new IllegalStateException("Scan is already in progress.");
|
||||
// Neither should this method be called if the adapter is not available.
|
||||
if (mBleScanner == null) throw new IllegalStateException("BLE is not available.");
|
||||
|
||||
if (DEBUG) Log.i(TAG, "startScan()");
|
||||
|
||||
// Collect MAC addresses from all associations.
|
||||
final Set<String> macAddresses = new HashSet<>();
|
||||
for (AssociationInfo association : mAssociationStore.getAssociations()) {
|
||||
// Beware that BT stack does not consider low-case MAC addresses valid, while
|
||||
// MacAddress.toString() return a low-case String.
|
||||
final String macAddress = association.getDeviceMacAddressAsString();
|
||||
if (macAddress != null) {
|
||||
macAddresses.add(macAddress);
|
||||
}
|
||||
}
|
||||
if (macAddresses.isEmpty()) {
|
||||
if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for.");
|
||||
return;
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")"
|
||||
+ "[" + String.join(", ", macAddresses) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
final List<ScanFilter> filters = new ArrayList<>(macAddresses.size());
|
||||
for (String macAddress : macAddresses) {
|
||||
final ScanFilter filter = new ScanFilter.Builder()
|
||||
.setDeviceAddress(macAddress)
|
||||
.build();
|
||||
filters.add(filter);
|
||||
}
|
||||
|
||||
mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
|
||||
mScanning = true;
|
||||
}
|
||||
|
||||
private void stopScanIfNeeded() {
|
||||
enforceInitialized();
|
||||
|
||||
if (DEBUG) Log.i(TAG, "stopScan()");
|
||||
if (!mScanning) {
|
||||
Log.d(TAG, " > not scanning.");
|
||||
return;
|
||||
}
|
||||
|
||||
mBleScanner.stopScan(mScanCallback);
|
||||
mScanning = false;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void notifyDeviceFound(@NonNull BluetoothDevice device) {
|
||||
if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
|
||||
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(device.getAddress());
|
||||
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
|
||||
|
||||
for (AssociationInfo association : associations) {
|
||||
mCallback.onBleCompanionDeviceFound(association.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void notifyDeviceLost(@NonNull BluetoothDevice device) {
|
||||
if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
|
||||
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(device.getAddress());
|
||||
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
|
||||
|
||||
for (AssociationInfo association : associations) {
|
||||
mCallback.onBleCompanionDeviceLost(association.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerBluetoothStateBroadcastReceiver(Context context) {
|
||||
final BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1);
|
||||
final int state = intent.getIntExtra(EXTRA_STATE, -1);
|
||||
|
||||
if (DEBUG) {
|
||||
// The action is either STATE_CHANGED or BLE_STATE_CHANGED.
|
||||
final String action =
|
||||
intent.getAction().replace("android.bluetooth.adapter.", "bt.");
|
||||
Log.d(TAG, "on(Broadcast)Receive() " + action + ": "
|
||||
+ nameForBtState(prevState) + "->" + nameForBtState(state));
|
||||
}
|
||||
|
||||
checkBleState();
|
||||
}
|
||||
};
|
||||
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_BLE_STATE_CHANGED);
|
||||
|
||||
context.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
private void enforceInitialized() {
|
||||
if (mBtAdapter != null) return;
|
||||
throw new IllegalStateException(getClass().getSimpleName() + " is not initialized");
|
||||
}
|
||||
|
||||
private final ScanCallback mScanCallback = new ScanCallback() {
|
||||
@MainThread
|
||||
@Override
|
||||
public void onScanResult(int callbackType, ScanResult result) {
|
||||
final BluetoothDevice device = result.getDevice();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType)
|
||||
+ " device=" + btDeviceToString(device));
|
||||
Log.v(TAG, " > scanResult=" + result);
|
||||
|
||||
final List<AssociationInfo> associations =
|
||||
mAssociationStore.getAssociationsByAddress(device.getAddress());
|
||||
Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray()));
|
||||
}
|
||||
|
||||
switch (callbackType) {
|
||||
case CALLBACK_TYPE_FIRST_MATCH:
|
||||
if (mMainThreadHandler.hasNotifyDeviceLostMessages(device)) {
|
||||
mMainThreadHandler.removeNotifyDeviceLostMessages(device);
|
||||
return;
|
||||
}
|
||||
|
||||
notifyDeviceFound(device);
|
||||
break;
|
||||
|
||||
case CALLBACK_TYPE_MATCH_LOST:
|
||||
mMainThreadHandler.sendNotifyDeviceLostDelayedMessage(device);
|
||||
break;
|
||||
|
||||
default:
|
||||
Slog.wtf(TAG, "Unexpected callback "
|
||||
+ nameForBleScanCallbackType(callbackType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@Override
|
||||
public void onScanFailed(int errorCode) {
|
||||
if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode));
|
||||
mScanning = false;
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
private class MainThreadHandler extends Handler {
|
||||
private static final int NOTIFY_DEVICE_LOST = 1;
|
||||
|
||||
MainThreadHandler() {
|
||||
super(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message message) {
|
||||
if (message.what != NOTIFY_DEVICE_LOST) return;
|
||||
|
||||
final BluetoothDevice device = (BluetoothDevice) message.obj;
|
||||
notifyDeviceLost(device);
|
||||
}
|
||||
|
||||
void sendNotifyDeviceLostDelayedMessage(BluetoothDevice device) {
|
||||
final Message message = obtainMessage(NOTIFY_DEVICE_LOST, device);
|
||||
sendMessageDelayed(message, NOTIFY_DEVICE_LOST_DELAY);
|
||||
}
|
||||
|
||||
boolean hasNotifyDeviceLostMessages(BluetoothDevice device) {
|
||||
return hasEqualMessages(NOTIFY_DEVICE_LOST, device);
|
||||
}
|
||||
|
||||
void removeNotifyDeviceLostMessages(BluetoothDevice device) {
|
||||
removeEqualMessages(NOTIFY_DEVICE_LOST, device);
|
||||
}
|
||||
}
|
||||
|
||||
private static String nameForBtState(int state) {
|
||||
return nameForState(state) + "(" + state + ")";
|
||||
}
|
||||
|
||||
private static String nameForBleScanCallbackType(int callbackType) {
|
||||
final String name;
|
||||
switch (callbackType) {
|
||||
case CALLBACK_TYPE_ALL_MATCHES:
|
||||
name = "ALL_MATCHES";
|
||||
break;
|
||||
case CALLBACK_TYPE_FIRST_MATCH:
|
||||
name = "FIRST_MATCH";
|
||||
break;
|
||||
case CALLBACK_TYPE_MATCH_LOST:
|
||||
name = "MATCH_LOST";
|
||||
break;
|
||||
default:
|
||||
name = "Unknown";
|
||||
}
|
||||
return name + "(" + callbackType + ")";
|
||||
}
|
||||
|
||||
private static String nameForBleScanErrorCode(int errorCode) {
|
||||
final String name;
|
||||
switch (errorCode) {
|
||||
case SCAN_FAILED_ALREADY_STARTED:
|
||||
name = "ALREADY_STARTED";
|
||||
break;
|
||||
case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
|
||||
name = "APPLICATION_REGISTRATION_FAILED";
|
||||
break;
|
||||
case SCAN_FAILED_INTERNAL_ERROR:
|
||||
name = "INTERNAL_ERROR";
|
||||
break;
|
||||
case SCAN_FAILED_FEATURE_UNSUPPORTED:
|
||||
name = "FEATURE_UNSUPPORTED";
|
||||
break;
|
||||
case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
|
||||
name = "OUT_OF_HARDWARE_RESOURCES";
|
||||
break;
|
||||
case SCAN_FAILED_SCANNING_TOO_FREQUENTLY:
|
||||
name = "SCANNING_TOO_FREQUENTLY";
|
||||
break;
|
||||
default:
|
||||
name = "Unknown";
|
||||
}
|
||||
return name + "(" + errorCode + ")";
|
||||
}
|
||||
|
||||
private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder()
|
||||
.setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
|
||||
.setScanMode(SCAN_MODE_LOW_POWER)
|
||||
.build();
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.companion.presence;
|
||||
|
||||
import static com.android.server.companion.presence.Utils.btDeviceToString;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
@@ -71,11 +73,11 @@ class BluetoothCompanionDeviceConnectionListener
|
||||
*/
|
||||
@Override
|
||||
public void onDeviceConnected(@NonNull BluetoothDevice device) {
|
||||
if (DEBUG) Log.i(TAG, "onDevice_Connected() " + toString(device));
|
||||
if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
|
||||
|
||||
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
|
||||
if (mAllConnectedDevices.put(macAddress, device) != null) {
|
||||
if (DEBUG) Log.w(TAG, "Device " + toString(device) + " is already connected.");
|
||||
if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,13 +93,15 @@ class BluetoothCompanionDeviceConnectionListener
|
||||
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
|
||||
@DisconnectReason int reason) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "onDevice_Disconnected() " + toString(device));
|
||||
Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
|
||||
Log.d(TAG, " reason=" + disconnectReasonText(reason));
|
||||
}
|
||||
|
||||
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
|
||||
if (mAllConnectedDevices.remove(macAddress) == null) {
|
||||
if (DEBUG) Log.w(TAG, "The device wasn't tracked as connected " + toString(device));
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,7 +113,7 @@ class BluetoothCompanionDeviceConnectionListener
|
||||
mAssociationStore.getAssociationsByAddress(device.getAddress());
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onDevice_ConnectivityChanged() " + toString(device)
|
||||
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
|
||||
+ " connected=" + connected);
|
||||
if (associations.isEmpty()) {
|
||||
Log.d(TAG, " > No CDM associations");
|
||||
@@ -137,6 +141,12 @@ class BluetoothCompanionDeviceConnectionListener
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssociationRemoved(AssociationInfo association) {
|
||||
// Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping
|
||||
// required.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
|
||||
if (DEBUG) {
|
||||
@@ -153,23 +163,4 @@ class BluetoothCompanionDeviceConnectionListener
|
||||
// This will be implemented when CDM support updating addresses.
|
||||
throw new IllegalArgumentException("Address changes are not supported.");
|
||||
}
|
||||
|
||||
private static String toString(@NonNull BluetoothDevice btDevice) {
|
||||
final StringBuilder sb = new StringBuilder(btDevice.getAddress());
|
||||
|
||||
sb.append(" [name=");
|
||||
final String name = btDevice.getName();
|
||||
if (name != null) {
|
||||
sb.append('\'').append(name).append('\'');
|
||||
} else {
|
||||
sb.append("null");
|
||||
}
|
||||
|
||||
final String alias = btDevice.getAlias();
|
||||
if (alias != null) {
|
||||
sb.append(", alias='").append(alias).append("'");
|
||||
}
|
||||
|
||||
return sb.append(']').toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.bluetooth.BluetoothDevice;
|
||||
|
||||
/** Utilities for working with Bluetooth and BLE devices. */
|
||||
class Utils {
|
||||
|
||||
/**
|
||||
* @return short String representation of {@link BluetoothDevice}.
|
||||
*/
|
||||
static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
|
||||
final StringBuilder sb = new StringBuilder(btDevice.getAddress());
|
||||
|
||||
sb.append(" [name=");
|
||||
final String name = btDevice.getName();
|
||||
if (name != null) {
|
||||
sb.append('\'').append(name).append('\'');
|
||||
} else {
|
||||
sb.append("null");
|
||||
}
|
||||
|
||||
final String alias = btDevice.getAlias();
|
||||
if (alias != null) {
|
||||
sb.append(", alias='").append(alias).append("'");
|
||||
}
|
||||
|
||||
return sb.append(']').toString();
|
||||
}
|
||||
|
||||
private Utils() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user