1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.companion.presence; 18 19 import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; 20 import static com.android.server.companion.presence.Utils.btDeviceToString; 21 22 import android.annotation.NonNull; 23 import android.annotation.SuppressLint; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.companion.AssociationInfo; 27 import android.net.MacAddress; 28 import android.os.Handler; 29 import android.os.HandlerExecutor; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 import android.util.Log; 33 import android.util.Slog; 34 import android.util.SparseArray; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.server.companion.AssociationStore; 38 39 import java.util.Arrays; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 46 @SuppressLint("LongLogTag") 47 public class BluetoothCompanionDeviceConnectionListener 48 extends BluetoothAdapter.BluetoothConnectionCallback 49 implements AssociationStore.OnChangeListener { 50 private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener"; 51 52 interface Callback { onBluetoothCompanionDeviceConnected(int associationId)53 void onBluetoothCompanionDeviceConnected(int associationId); 54 onBluetoothCompanionDeviceDisconnected(int associationId)55 void onBluetoothCompanionDeviceDisconnected(int associationId); 56 } 57 58 private final UserManager mUserManager; 59 private final @NonNull AssociationStore mAssociationStore; 60 private final @NonNull Callback mCallback; 61 /** A set of ALL connected BT device (not only companion.) */ 62 private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); 63 64 /** 65 * A structure hold the connected BT devices that are pending to be reported to the companion 66 * app when the user unlocks the local device per userId. 67 */ 68 @GuardedBy("mPendingConnectedDevices") 69 @NonNull 70 final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>(); 71 BluetoothCompanionDeviceConnectionListener(UserManager userManager, @NonNull AssociationStore associationStore, @NonNull Callback callback)72 BluetoothCompanionDeviceConnectionListener(UserManager userManager, 73 @NonNull AssociationStore associationStore, @NonNull Callback callback) { 74 mAssociationStore = associationStore; 75 mCallback = callback; 76 mUserManager = userManager; 77 } 78 init(@onNull BluetoothAdapter btAdapter)79 public void init(@NonNull BluetoothAdapter btAdapter) { 80 if (DEBUG) Log.i(TAG, "init()"); 81 82 btAdapter.registerBluetoothConnectionCallback( 83 new HandlerExecutor(Handler.getMain()), /* callback */this); 84 mAssociationStore.registerListener(this); 85 } 86 87 /** 88 * Overrides 89 * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. 90 */ 91 @Override onDeviceConnected(@onNull BluetoothDevice device)92 public void onDeviceConnected(@NonNull BluetoothDevice device) { 93 if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device)); 94 95 final MacAddress macAddress = MacAddress.fromString(device.getAddress()); 96 final int userId = UserHandle.myUserId(); 97 98 if (mAllConnectedDevices.put(macAddress, device) != null) { 99 if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected."); 100 return; 101 } 102 // Try to bind and notify the app after the phone is unlocked. 103 if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.myUserId())) { 104 Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify " 105 + "the application when the phone is unlocked"); 106 synchronized (mPendingConnectedDevices) { 107 Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get( 108 userId, new HashSet<>()); 109 bluetoothDevices.add(device); 110 mPendingConnectedDevices.put(userId, bluetoothDevices); 111 } 112 113 } else { 114 onDeviceConnectivityChanged(device, true); 115 } 116 } 117 118 /** 119 * Overrides 120 * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}. 121 * Also invoked when user turns BT off while the device is connected. 122 */ 123 @Override onDeviceDisconnected(@onNull BluetoothDevice device, int reason)124 public void onDeviceDisconnected(@NonNull BluetoothDevice device, 125 int reason) { 126 if (DEBUG) { 127 Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device)); 128 Log.d(TAG, " reason=" + disconnectReasonToString(reason)); 129 } 130 131 final MacAddress macAddress = MacAddress.fromString(device.getAddress()); 132 final int userId = UserHandle.myUserId(); 133 134 if (mAllConnectedDevices.remove(macAddress) == null) { 135 if (DEBUG) { 136 Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device)); 137 } 138 return; 139 } 140 141 // Do not need to report the connectivity since the user is not unlock the phone so 142 // that cdm is not bind with the app yet. 143 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 144 synchronized (mPendingConnectedDevices) { 145 Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(userId); 146 if (bluetoothDevices != null) { 147 bluetoothDevices.remove(device); 148 } 149 } 150 151 return; 152 } 153 154 onDeviceConnectivityChanged(device, false); 155 } 156 onDeviceConnectivityChanged(@onNull BluetoothDevice device, boolean connected)157 private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) { 158 final List<AssociationInfo> associations = 159 mAssociationStore.getAssociationsByAddress(device.getAddress()); 160 161 if (DEBUG) { 162 Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) 163 + " connected=" + connected); 164 if (associations.isEmpty()) { 165 Log.d(TAG, " > No CDM associations"); 166 } else { 167 Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); 168 } 169 } 170 171 for (AssociationInfo association : associations) { 172 final int id = association.getId(); 173 if (connected) { 174 mCallback.onBluetoothCompanionDeviceConnected(id); 175 } else { 176 mCallback.onBluetoothCompanionDeviceDisconnected(id); 177 } 178 } 179 } 180 181 @Override onAssociationAdded(AssociationInfo association)182 public void onAssociationAdded(AssociationInfo association) { 183 if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association); 184 185 if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) { 186 mCallback.onBluetoothCompanionDeviceConnected(association.getId()); 187 } 188 } 189 190 @Override onAssociationRemoved(AssociationInfo association)191 public void onAssociationRemoved(AssociationInfo association) { 192 // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping 193 // required. 194 } 195 196 @Override onAssociationUpdated(AssociationInfo association, boolean addressChanged)197 public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { 198 if (DEBUG) { 199 Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged 200 + " " + association); 201 } 202 203 if (!addressChanged) { 204 // Don't need to do anything. 205 return; 206 } 207 208 // At the moment CDM does allow changing association addresses, so we will never come here. 209 // This will be implemented when CDM support updating addresses. 210 throw new IllegalArgumentException("Address changes are not supported."); 211 } 212 } 213