• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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