Merge changes from topic "cdm-role-grant"

* changes:
  [5/X] Introduce BleCompanionDeviceScanner
  Grant CDM roles when association is created
This commit is contained in:
TreeHugger Robot
2022-01-21 20:04:43 +00:00
committed by Android (Google) Code Review
6 changed files with 517 additions and 56 deletions

View File

@@ -254,9 +254,14 @@ class AssociationRequestsProcessor {
private AssociationInfo createAssociationAndNotifyApplication(
@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
@Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) {
final AssociationInfo association = mService.createAssociation(userId, packageName,
macAddress, request.getDisplayName(), request.getDeviceProfile(),
request.isSelfManaged());
final AssociationInfo association;
final long callingIdentity = Binder.clearCallingIdentity();
try {
association = mService.createAssociation(userId, packageName, macAddress,
request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged());
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
try {
callback.onAssociationCreated(association);

View File

@@ -787,6 +787,12 @@ public class CompanionDeviceManagerService extends SystemService
Slog.i(LOG_TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
// If the "Device Profile" is specified, make the companion application a holder of the
// corresponding role.
if (deviceProfile != null) {
addRoleHolderForAssociation(getContext(), association);
}
updateSpecialAccessPermissionForAssociatedPackage(association);
return association;
@@ -930,14 +936,8 @@ public class CompanionDeviceManagerService extends SystemService
exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
if (!association.isSelfManaged()) {
if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) {
addRoleHolderForAssociation(getContext(), association);
}
if (association.isNotifyOnDeviceNearby()) {
restartBleScan();
}
if (association.isNotifyOnDeviceNearby()) {
restartBleScan();
}
}
@@ -983,19 +983,7 @@ public class CompanionDeviceManagerService extends SystemService
void onDeviceConnected(String address) {
Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
mCurrentlyConnectedDevices.add(address);
for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) {
if (association.getDeviceProfile() != null) {
Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
+ " to " + association.getPackageName()
+ " due to device connected: " + association.getDeviceMacAddress());
addRoleHolderForAssociation(getContext(), association);
}
}
onDeviceNearby(address);
}

View File

@@ -73,20 +73,12 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand {
}
break;
case "simulate_connect": {
mService.onDeviceConnected(getNextArgRequired());
}
break;
case "simulate_disconnect": {
mService.onDeviceDisconnected(getNextArgRequired());
}
break;
case "clear-association-memory-cache": {
mService.persistState();
mService.loadAssociationsFromDisk();
}
break;
default:
return handleDefaultCommands(cmd);
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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() {
}
}