blob: a5410e448c7bcc695fb61990f2d69439d8b3faa2 [file] [log] [blame]
/*
* 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 com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.net.MacAddress;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@SuppressLint("LongLogTag")
public class BluetoothCompanionDeviceConnectionListener
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener";
interface Callback {
void onBluetoothCompanionDeviceConnected(int associationId);
void onBluetoothCompanionDeviceDisconnected(int associationId);
}
private final UserManager mUserManager;
private final @NonNull AssociationStore mAssociationStore;
private final @NonNull Callback mCallback;
/** A set of ALL connected BT device (not only companion.) */
private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
/**
* A structure hold the connected BT devices that are pending to be reported to the companion
* app when the user unlocks the local device per userId.
*/
@GuardedBy("mPendingConnectedDevices")
@NonNull
final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
BluetoothCompanionDeviceConnectionListener(UserManager userManager,
@NonNull AssociationStore associationStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mCallback = callback;
mUserManager = userManager;
}
public void init(@NonNull BluetoothAdapter btAdapter) {
if (DEBUG) Log.i(TAG, "init()");
btAdapter.registerBluetoothConnectionCallback(
new HandlerExecutor(Handler.getMain()), /* callback */this);
mAssociationStore.registerListener(this);
}
/**
* Overrides
* {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}.
*/
@Override
public void onDeviceConnected(@NonNull BluetoothDevice device) {
if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.put(macAddress, device) != null) {
if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
return;
}
// Try to bind and notify the app after the phone is unlocked.
if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify "
+ "the application when the phone is unlocked");
synchronized (mPendingConnectedDevices) {
Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(
userId, new HashSet<>());
bluetoothDevices.add(device);
mPendingConnectedDevices.put(userId, bluetoothDevices);
}
} else {
onDeviceConnectivityChanged(device, true);
}
}
/**
* Overrides
* {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}.
* Also invoked when user turns BT off while the device is connected.
*/
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
int reason) {
if (DEBUG) {
Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
Log.d(TAG, " reason=" + disconnectReasonToString(reason));
}
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.remove(macAddress) == null) {
if (DEBUG) {
Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
}
return;
}
// Do not need to report the connectivity since the user is not unlock the phone so
// that cdm is not bind with the app yet.
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
synchronized (mPendingConnectedDevices) {
Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(userId);
if (bluetoothDevices != null) {
bluetoothDevices.remove(device);
}
}
return;
}
onDeviceConnectivityChanged(device, false);
}
private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
final List<AssociationInfo> associations =
mAssociationStore.getAssociationsByAddress(device.getAddress());
if (DEBUG) {
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
+ " connected=" + connected);
if (associations.isEmpty()) {
Log.d(TAG, " > No CDM associations");
} else {
Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
}
}
for (AssociationInfo association : associations) {
final int id = association.getId();
if (connected) {
mCallback.onBluetoothCompanionDeviceConnected(id);
} else {
mCallback.onBluetoothCompanionDeviceDisconnected(id);
}
}
}
@Override
public void onAssociationAdded(AssociationInfo association) {
if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
mCallback.onBluetoothCompanionDeviceConnected(association.getId());
}
}
@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) {
Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged
+ " " + association);
}
if (!addressChanged) {
// Don't need to do anything.
return;
}
// At the moment CDM does allow changing association addresses, so we will never come here.
// This will be implemented when CDM support updating addresses.
throw new IllegalArgumentException("Address changes are not supported.");
}
}