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