1 /*
2  * Copyright (C) 2013 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 package com.android.bluetooth.gatt;
17 
18 import android.bluetooth.le.AdvertiseData;
19 import android.bluetooth.le.AdvertisingSetParameters;
20 import android.bluetooth.le.PeriodicAdvertisingParameters;
21 import android.content.Context;
22 import android.os.Binder;
23 import android.os.IBinder;
24 import android.os.IInterface;
25 import android.os.RemoteException;
26 import android.os.SystemClock;
27 import android.os.UserHandle;
28 import android.os.WorkSource;
29 import android.util.Log;
30 
31 import com.android.bluetooth.BluetoothMethodProxy;
32 import com.android.bluetooth.flags.Flags;
33 import com.android.bluetooth.le_scan.AppScanStats;
34 import com.android.bluetooth.le_scan.TransitionalScanHelper;
35 import com.android.bluetooth.le_scan.TransitionalScanHelper.PendingIntentInfo;
36 import com.android.internal.annotations.GuardedBy;
37 
38 import com.google.common.collect.EvictingQueue;
39 
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.NoSuchElementException;
47 import java.util.Set;
48 import java.util.UUID;
49 import java.util.function.Predicate;
50 
51 /**
52  * Helper class that keeps track of registered GATT applications. This class manages application
53  * callbacks and keeps track of GATT connections.
54  */
55 public class ContextMap<C, T> {
56     private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
57 
58     /** Connection class helps map connection IDs to device addresses. */
59     public static class Connection {
60         public int connId;
61         public String address;
62         public int appId;
63         public long startTime;
64 
Connection(int connId, String address, int appId)65         Connection(int connId, String address, int appId) {
66             this.connId = connId;
67             this.address = address;
68             this.appId = appId;
69             this.startTime = SystemClock.elapsedRealtime();
70         }
71     }
72 
73     /** Application entry mapping UUIDs to appIDs and callbacks. */
74     public class App {
75         /** The UUID of the application */
76         public UUID uuid;
77 
78         /** The id of the application */
79         public int id;
80 
81         /** The package name of the application */
82         public String name;
83 
84         /** Statistics for this app */
85         public AppScanStats appScanStats;
86 
87         /** Application callbacks */
88         public C callback;
89 
90         /** Context information */
91         public T info;
92 
93         /** Death recipient */
94         private IBinder.DeathRecipient mDeathRecipient;
95 
96         /** Flag to signal that transport is congested */
97         public Boolean isCongested = false;
98 
99         /** Whether the calling app has location permission */
100         public boolean hasLocationPermission;
101 
102         /** Whether the calling app has bluetooth privileged permission */
103         public boolean hasBluetoothPrivilegedPermission;
104 
105         /** The user handle of the app that started the scan */
106         public UserHandle mUserHandle;
107 
108         /** Whether the calling app has the network settings permission */
109         public boolean mHasNetworkSettingsPermission;
110 
111         /** Whether the calling app has the network setup wizard permission */
112         public boolean mHasNetworkSetupWizardPermission;
113 
114         /** Whether the calling app has the network setup wizard permission */
115         public boolean mHasScanWithoutLocationPermission;
116 
117         /** Whether the calling app has disavowed the use of bluetooth for location */
118         public boolean mHasDisavowedLocation;
119 
120         public boolean mEligibleForSanitizedExposureNotification;
121 
122         public List<String> mAssociatedDevices;
123 
124         /** Internal callback info queue, waiting to be send on congestion clear */
125         private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
126 
127         /** Creates a new app context. */
App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)128         App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) {
129             this.uuid = uuid;
130             this.callback = callback;
131             this.info = info;
132             this.name = name;
133             this.appScanStats = appScanStats;
134         }
135 
136         /** Creates a new app context for advertiser. */
App(int id, C callback, String name)137         App(int id, C callback, String name) {
138             this.id = id;
139             this.callback = callback;
140             this.name = name;
141         }
142 
143         /** Link death recipient */
linkToDeath(IBinder.DeathRecipient deathRecipient)144         public void linkToDeath(IBinder.DeathRecipient deathRecipient) {
145             // It might not be a binder object
146             if (callback == null) {
147                 return;
148             }
149             try {
150                 IBinder binder = ((IInterface) callback).asBinder();
151                 binder.linkToDeath(deathRecipient, 0);
152                 mDeathRecipient = deathRecipient;
153             } catch (RemoteException e) {
154                 Log.e(TAG, "Unable to link deathRecipient for app id " + id);
155             }
156         }
157 
158         /** Unlink death recipient */
unlinkToDeath()159         public void unlinkToDeath() {
160             if (mDeathRecipient != null) {
161                 try {
162                     IBinder binder = ((IInterface) callback).asBinder();
163                     binder.unlinkToDeath(mDeathRecipient, 0);
164                 } catch (NoSuchElementException e) {
165                     Log.e(TAG, "Unable to unlink deathRecipient for app id " + id);
166                 }
167             }
168         }
169 
queueCallback(CallbackInfo callbackInfo)170         public void queueCallback(CallbackInfo callbackInfo) {
171             mCongestionQueue.add(callbackInfo);
172         }
173 
popQueuedCallback()174         public CallbackInfo popQueuedCallback() {
175             if (mCongestionQueue.size() == 0) {
176                 return null;
177             }
178             return mCongestionQueue.remove(0);
179         }
180     }
181 
182     /** Our internal application list */
183     private final Object mAppsLock = new Object();
184 
185     @GuardedBy("mAppsLock")
186     private List<App> mApps = new ArrayList<App>();
187 
188     /** Internal map to keep track of logging information by app name */
189     private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
190 
191     /** Internal map to keep track of logging information by advertise id */
192     private final Map<Integer, AppAdvertiseStats> mAppAdvertiseStats =
193             new HashMap<Integer, AppAdvertiseStats>();
194 
195     private static final int ADVERTISE_STATE_MAX_SIZE = 5;
196 
197     private final EvictingQueue<AppAdvertiseStats> mLastAdvertises =
198             EvictingQueue.create(ADVERTISE_STATE_MAX_SIZE);
199 
200     /** Internal list of connected devices */
201     private List<Connection> mConnections = new ArrayList<Connection>();
202 
203     private final Object mConnectionsLock = new Object();
204 
205     /** Add an entry to the application context list. */
add( UUID uuid, WorkSource workSource, C callback, PendingIntentInfo piInfo, Context context, TransitionalScanHelper scanHelper)206     public App add(
207             UUID uuid,
208             WorkSource workSource,
209             C callback,
210             PendingIntentInfo piInfo,
211             Context context,
212             TransitionalScanHelper scanHelper) {
213         int appUid;
214         String appName = null;
215         if (piInfo != null) {
216             appUid = piInfo.callingUid;
217             appName = piInfo.callingPackage;
218         } else {
219             appUid = Binder.getCallingUid();
220             appName = context.getPackageManager().getNameForUid(appUid);
221         }
222         if (appName == null) {
223             // Assign an app name if one isn't found
224             appName = "Unknown App (UID: " + appUid + ")";
225         }
226         synchronized (mAppsLock) {
227             // TODO(b/327849650): AppScanStats appears to be only needed for the ScannerMap.
228             //                    Consider refactoring this.
229             AppScanStats appScanStats = mAppScanStats.get(appUid);
230             if (appScanStats == null) {
231                 appScanStats = new AppScanStats(appName, workSource, this, context, scanHelper);
232                 mAppScanStats.put(appUid, appScanStats);
233             }
234             App app = new App(uuid, callback, (T) piInfo, appName, appScanStats);
235             mApps.add(app);
236             appScanStats.isRegistered = true;
237             return app;
238         }
239     }
240 
241     /** Add an entry to the application context list for advertiser. */
add(int id, C callback, GattService service)242     public App add(int id, C callback, GattService service) {
243         int appUid = Binder.getCallingUid();
244         String appName = service.getPackageManager().getNameForUid(appUid);
245         if (appName == null) {
246             // Assign an app name if one isn't found
247             appName = "Unknown App (UID: " + appUid + ")";
248         }
249 
250         synchronized (mAppsLock) {
251             synchronized (this) {
252                 if (!mAppAdvertiseStats.containsKey(id)) {
253                     AppAdvertiseStats appAdvertiseStats =
254                             BluetoothMethodProxy.getInstance()
255                                     .createAppAdvertiseStats(id, appName, this, service);
256                     mAppAdvertiseStats.put(id, appAdvertiseStats);
257                 }
258             }
259             App app = getById(appUid);
260             if (app == null) {
261                 app = new App(appUid, callback, appName);
262                 mApps.add(app);
263             }
264             return app;
265         }
266     }
267 
268     /** Remove the context for a given UUID */
remove(UUID uuid)269     public void remove(UUID uuid) {
270         synchronized (mAppsLock) {
271             Iterator<App> i = mApps.iterator();
272             while (i.hasNext()) {
273                 App entry = i.next();
274                 if (entry.uuid.equals(uuid)) {
275                     entry.unlinkToDeath();
276                     entry.appScanStats.isRegistered = false;
277                     i.remove();
278                     break;
279                 }
280             }
281         }
282     }
283 
284     /** Remove the context for a given application ID. */
remove(int id)285     public void remove(int id) {
286         boolean find = false;
287         synchronized (mAppsLock) {
288             Iterator<App> i = mApps.iterator();
289             while (i.hasNext()) {
290                 App entry = i.next();
291                 if (entry.id == id) {
292                     find = true;
293                     entry.unlinkToDeath();
294                     entry.appScanStats.isRegistered = false;
295                     i.remove();
296                     break;
297                 }
298             }
299         }
300         if (find) {
301             removeConnectionsByAppId(id);
302         }
303     }
304 
getAllAppsIds()305     public List<Integer> getAllAppsIds() {
306         List<Integer> appIds = new ArrayList();
307         synchronized (mAppsLock) {
308             for (App entry : mApps) {
309                 appIds.add(entry.id);
310             }
311         }
312         return appIds;
313     }
314 
315     /** Add a new connection for a given application ID. */
addConnection(int id, int connId, String address)316     void addConnection(int id, int connId, String address) {
317         synchronized (mConnectionsLock) {
318             App entry = getById(id);
319             if (entry != null) {
320                 mConnections.add(new Connection(connId, address, id));
321             }
322         }
323     }
324 
325     /** Remove a connection with the given ID. */
removeConnection(int id, int connId)326     void removeConnection(int id, int connId) {
327         synchronized (mConnectionsLock) {
328             if (Flags.bleContextMapRemoveFix()) {
329                 mConnections.removeIf(conn -> conn.appId == id && conn.connId == connId);
330             } else {
331                 Iterator<Connection> i = mConnections.iterator();
332                 while (i.hasNext()) {
333                     Connection connection = i.next();
334                     if (connection.connId == connId) {
335                         i.remove();
336                         break;
337                     }
338                 }
339             }
340         }
341     }
342 
343     /** Remove all connections for a given application ID. */
removeConnectionsByAppId(int appId)344     void removeConnectionsByAppId(int appId) {
345         synchronized (mConnectionsLock) {
346             mConnections.removeIf(conn -> conn.appId == appId);
347         }
348     }
349 
getAppByPredicate(Predicate<App> predicate)350     private App getAppByPredicate(Predicate<App> predicate) {
351         synchronized (mAppsLock) {
352             // Intentionally using a for-loop over a stream for performance.
353             for (App app : mApps) {
354                 if (predicate.test(app)) {
355                     return app;
356                 }
357             }
358             return null;
359         }
360     }
361 
362     /** Get an application context by ID. */
getById(int id)363     public App getById(int id) {
364         App app = getAppByPredicate(entry -> entry.id == id);
365         if (app == null) {
366             Log.e(TAG, "Context not found for ID " + id);
367         }
368         return app;
369     }
370 
371     /** Get an application context by UUID. */
getByUuid(UUID uuid)372     public App getByUuid(UUID uuid) {
373         App app = getAppByPredicate(entry -> entry.uuid.equals(uuid));
374         if (app == null) {
375             Log.e(TAG, "Context not found for UUID " + uuid);
376         }
377         return app;
378     }
379 
380     /** Get an application context by the calling Apps name. */
getByName(String name)381     public App getByName(String name) {
382         App app = getAppByPredicate(entry -> entry.name.equals(name));
383         if (app == null) {
384             Log.e(TAG, "Context not found for name " + name);
385         }
386         return app;
387     }
388 
389     /** Get an application context by the context info object. */
getByContextInfo(T contextInfo)390     public App getByContextInfo(T contextInfo) {
391         App app = getAppByPredicate(entry -> entry.info != null && entry.info.equals(contextInfo));
392         if (app == null) {
393             Log.e(TAG, "Context not found for info " + contextInfo);
394         }
395         return app;
396     }
397 
398     /** Get Logging info by ID */
getAppScanStatsById(int id)399     public AppScanStats getAppScanStatsById(int id) {
400         App temp = getById(id);
401         if (temp != null) {
402             return temp.appScanStats;
403         }
404         return null;
405     }
406 
407     /** Get Logging info by application UID */
getAppScanStatsByUid(int uid)408     public AppScanStats getAppScanStatsByUid(int uid) {
409         return mAppScanStats.get(uid);
410     }
411 
412     /** Remove the context for a given application ID. */
removeAppAdvertiseStats(int id)413     void removeAppAdvertiseStats(int id) {
414         synchronized (this) {
415             mAppAdvertiseStats.remove(id);
416         }
417     }
418 
419     /** Get Logging info by ID */
getAppAdvertiseStatsById(int id)420     AppAdvertiseStats getAppAdvertiseStatsById(int id) {
421         synchronized (this) {
422             return mAppAdvertiseStats.get(id);
423         }
424     }
425 
426     /** update the advertiser ID by the regiseter ID */
setAdvertiserIdByRegId(int regId, int advertiserId)427     void setAdvertiserIdByRegId(int regId, int advertiserId) {
428         synchronized (this) {
429             AppAdvertiseStats stats = mAppAdvertiseStats.get(regId);
430             if (stats == null) {
431                 return;
432             }
433             stats.setId(advertiserId);
434             mAppAdvertiseStats.remove(regId);
435             mAppAdvertiseStats.put(advertiserId, stats);
436         }
437     }
438 
recordAdvertiseStart( int id, AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents)439     void recordAdvertiseStart(
440             int id,
441             AdvertisingSetParameters parameters,
442             AdvertiseData advertiseData,
443             AdvertiseData scanResponse,
444             PeriodicAdvertisingParameters periodicParameters,
445             AdvertiseData periodicData,
446             int duration,
447             int maxExtAdvEvents) {
448         synchronized (this) {
449             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
450             if (stats == null) {
451                 return;
452             }
453             stats.recordAdvertiseStart(
454                     parameters,
455                     advertiseData,
456                     scanResponse,
457                     periodicParameters,
458                     periodicData,
459                     duration,
460                     maxExtAdvEvents);
461             int advertiseInstanceCount = mAppAdvertiseStats.size();
462             Log.d(TAG, "advertiseInstanceCount is " + advertiseInstanceCount);
463             AppAdvertiseStats.recordAdvertiseInstanceCount(advertiseInstanceCount);
464         }
465     }
466 
recordAdvertiseStop(int id)467     void recordAdvertiseStop(int id) {
468         synchronized (this) {
469             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
470             if (stats == null) {
471                 return;
472             }
473             stats.recordAdvertiseStop();
474             mAppAdvertiseStats.remove(id);
475             mLastAdvertises.add(stats);
476         }
477     }
478 
enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents)479     void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) {
480         synchronized (this) {
481             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
482             if (stats == null) {
483                 return;
484             }
485             stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents);
486         }
487     }
488 
setAdvertisingData(int id, AdvertiseData data)489     void setAdvertisingData(int id, AdvertiseData data) {
490         synchronized (this) {
491             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
492             if (stats == null) {
493                 return;
494             }
495             stats.setAdvertisingData(data);
496         }
497     }
498 
setScanResponseData(int id, AdvertiseData data)499     void setScanResponseData(int id, AdvertiseData data) {
500         synchronized (this) {
501             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
502             if (stats == null) {
503                 return;
504             }
505             stats.setScanResponseData(data);
506         }
507     }
508 
setAdvertisingParameters(int id, AdvertisingSetParameters parameters)509     void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) {
510         synchronized (this) {
511             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
512             if (stats == null) {
513                 return;
514             }
515             stats.setAdvertisingParameters(parameters);
516         }
517     }
518 
setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters)519     void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) {
520         synchronized (this) {
521             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
522             if (stats == null) {
523                 return;
524             }
525             stats.setPeriodicAdvertisingParameters(parameters);
526         }
527     }
528 
setPeriodicAdvertisingData(int id, AdvertiseData data)529     void setPeriodicAdvertisingData(int id, AdvertiseData data) {
530         synchronized (this) {
531             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
532             if (stats == null) {
533                 return;
534             }
535             stats.setPeriodicAdvertisingData(data);
536         }
537     }
538 
onPeriodicAdvertiseEnabled(int id, boolean enable)539     void onPeriodicAdvertiseEnabled(int id, boolean enable) {
540         synchronized (this) {
541             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
542             if (stats == null) {
543                 return;
544             }
545             stats.onPeriodicAdvertiseEnabled(enable);
546         }
547     }
548 
549     /** Get the device addresses for all connected devices */
getConnectedDevices()550     Set<String> getConnectedDevices() {
551         Set<String> addresses = new HashSet<String>();
552         synchronized (mConnectionsLock) {
553             for (Connection connection : mConnections) {
554                 addresses.add(connection.address);
555             }
556         }
557         return addresses;
558     }
559 
560     /** Get an application context by a connection ID. */
getByConnId(int connId)561     App getByConnId(int connId) {
562         int appId = -1;
563         synchronized (mConnectionsLock) {
564             for (Connection connection : mConnections) {
565                 if (connection.connId == connId) {
566                     appId = connection.appId;
567                     break;
568                 }
569             }
570         }
571         if (appId >= 0) {
572             return getById(appId);
573         }
574         return null;
575     }
576 
577     /** Returns a connection ID for a given device address. */
connIdByAddress(int id, String address)578     Integer connIdByAddress(int id, String address) {
579         App entry = getById(id);
580         if (entry == null) {
581             return null;
582         }
583         synchronized (mConnectionsLock) {
584             for (Connection connection : mConnections) {
585                 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
586                     return connection.connId;
587                 }
588             }
589         }
590         return null;
591     }
592 
593     /** Returns the device address for a given connection ID. */
addressByConnId(int connId)594     String addressByConnId(int connId) {
595         synchronized (mConnectionsLock) {
596             for (Connection connection : mConnections) {
597                 if (connection.connId == connId) {
598                     return connection.address;
599                 }
600             }
601         }
602         return null;
603     }
604 
getConnectionByApp(int appId)605     public List<Connection> getConnectionByApp(int appId) {
606         List<Connection> currentConnections = new ArrayList<Connection>();
607         synchronized (mConnectionsLock) {
608             for (Connection connection : mConnections) {
609                 if (connection.appId == appId) {
610                     currentConnections.add(connection);
611                 }
612             }
613         }
614         return currentConnections;
615     }
616 
617     /** Erases all application context entries. */
clear()618     public void clear() {
619         synchronized (mAppsLock) {
620             for (App entry : mApps) {
621                 entry.unlinkToDeath();
622                 if (entry.appScanStats != null) {
623                     entry.appScanStats.isRegistered = false;
624                 }
625             }
626             mApps.clear();
627         }
628 
629         synchronized (mConnectionsLock) {
630             mConnections.clear();
631         }
632 
633         synchronized (this) {
634             mAppAdvertiseStats.clear();
635             mLastAdvertises.clear();
636         }
637     }
638 
639     /** Returns connect device map with addr and appid */
getConnectedMap()640     Map<Integer, String> getConnectedMap() {
641         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
642         synchronized (mConnectionsLock) {
643             for (Connection conn : mConnections) {
644                 connectedmap.put(conn.appId, conn.address);
645             }
646         }
647         return connectedmap;
648     }
649 
650     /** Logs debug information. */
dump(StringBuilder sb)651     protected void dump(StringBuilder sb) {
652         sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
653         for (AppScanStats appScanStats : mAppScanStats.values()) {
654             appScanStats.dumpToString(sb);
655         }
656     }
657 
658     /** Logs advertiser debug information. */
dumpAdvertiser(StringBuilder sb)659     void dumpAdvertiser(StringBuilder sb) {
660         synchronized (this) {
661             if (!mLastAdvertises.isEmpty()) {
662                 sb.append("\n  last " + mLastAdvertises.size() + " advertising:");
663                 for (AppAdvertiseStats stats : mLastAdvertises) {
664                     AppAdvertiseStats.dumpToString(sb, stats);
665                 }
666                 sb.append("\n");
667             }
668 
669             if (!mAppAdvertiseStats.isEmpty()) {
670                 sb.append(
671                         "  Total number of ongoing advertising                   : "
672                                 + mAppAdvertiseStats.size());
673                 sb.append("\n  Ongoing advertising:");
674                 for (Integer key : mAppAdvertiseStats.keySet()) {
675                     AppAdvertiseStats stats = mAppAdvertiseStats.get(key);
676                     AppAdvertiseStats.dumpToString(sb, stats);
677                 }
678             }
679             sb.append("\n");
680         }
681         Log.d(TAG, sb.toString());
682     }
683 }
684