• 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     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     /**
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     private final Object mConnectionsLock = new Object();
211 
212     /**
213      * Add an entry to the application context list.
214      */
add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)215     App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) {
216         int appUid = Binder.getCallingUid();
217         String appName = service.getPackageManager().getNameForUid(appUid);
218         if (appName == null) {
219             // Assign an app name if one isn't found
220             appName = "Unknown App (UID: " + appUid + ")";
221         }
222         synchronized (mAppsLock) {
223             AppScanStats appScanStats = mAppScanStats.get(appUid);
224             if (appScanStats == null) {
225                 appScanStats = new AppScanStats(appName, workSource, this, service);
226                 mAppScanStats.put(appUid, appScanStats);
227             }
228             App app = new App(uuid, callback, info, appName, appScanStats);
229             mApps.add(app);
230             appScanStats.isRegistered = true;
231             return app;
232         }
233     }
234 
235     /**
236      * Add an entry to the application context list for advertiser.
237      */
add(int id, C callback, GattService service)238     App add(int id, C callback, GattService service) {
239         int appUid = Binder.getCallingUid();
240         String appName = service.getPackageManager().getNameForUid(appUid);
241         if (appName == null) {
242             // Assign an app name if one isn't found
243             appName = "Unknown App (UID: " + appUid + ")";
244         }
245 
246         synchronized (mAppsLock) {
247             synchronized (this) {
248                 if (!mAppAdvertiseStats.containsKey(id)) {
249                     AppAdvertiseStats appAdvertiseStats = BluetoothMethodProxy.getInstance()
250                             .createAppAdvertiseStats(appUid, id, appName, this, service);
251                     mAppAdvertiseStats.put(id, appAdvertiseStats);
252                 }
253             }
254             App app = getById(appUid);
255             if (app == null) {
256                 app = new App(appUid, callback, appName);
257                 mApps.add(app);
258             }
259             return app;
260         }
261     }
262 
263     /**
264      * Remove the context for a given UUID
265      */
remove(UUID uuid)266     void remove(UUID uuid) {
267         synchronized (mAppsLock) {
268             Iterator<App> i = mApps.iterator();
269             while (i.hasNext()) {
270                 App entry = i.next();
271                 if (entry.uuid.equals(uuid)) {
272                     entry.unlinkToDeath();
273                     entry.appScanStats.isRegistered = false;
274                     i.remove();
275                     break;
276                 }
277             }
278         }
279     }
280 
281     /**
282      * Remove the context for a given application ID.
283      */
remove(int id)284     void remove(int id) {
285         boolean find = false;
286         synchronized (mAppsLock) {
287             Iterator<App> i = mApps.iterator();
288             while (i.hasNext()) {
289                 App entry = i.next();
290                 if (entry.id == id) {
291                     find = true;
292                     entry.unlinkToDeath();
293                     entry.appScanStats.isRegistered = false;
294                     i.remove();
295                     break;
296                 }
297             }
298         }
299         if (find) {
300             removeConnectionsByAppId(id);
301         }
302     }
303 
getAllAppsIds()304     List<Integer> getAllAppsIds() {
305         List<Integer> appIds = new ArrayList();
306         synchronized (mAppsLock) {
307             Iterator<App> i = mApps.iterator();
308             while (i.hasNext()) {
309                 App entry = i.next();
310                 appIds.add(entry.id);
311             }
312         }
313         return appIds;
314     }
315 
316     /**
317      * Add a new connection for a given application ID.
318      */
addConnection(int id, int connId, String address)319     void addConnection(int id, int connId, String address) {
320         synchronized (mConnectionsLock) {
321             App entry = getById(id);
322             if (entry != null) {
323                 mConnections.add(new Connection(connId, address, id));
324             }
325         }
326     }
327 
328     /**
329      * Remove a connection with the given ID.
330      */
removeConnection(int id, int connId)331     void removeConnection(int id, int connId) {
332         synchronized (mConnectionsLock) {
333             Iterator<Connection> i = mConnections.iterator();
334             while (i.hasNext()) {
335                 Connection connection = i.next();
336                 if (connection.connId == connId) {
337                     i.remove();
338                     break;
339                 }
340             }
341         }
342     }
343 
344     /**
345      * Remove all connections for a given application ID.
346      */
removeConnectionsByAppId(int appId)347     void removeConnectionsByAppId(int appId) {
348         synchronized (mConnectionsLock) {
349             Iterator<Connection> i = mConnections.iterator();
350             while (i.hasNext()) {
351                 Connection connection = i.next();
352                 if (connection.appId == appId) {
353                     i.remove();
354                 }
355             }
356         }
357     }
358 
359     /**
360      * Get an application context by ID.
361      */
getById(int id)362     App getById(int id) {
363         synchronized (mAppsLock) {
364             Iterator<App> i = mApps.iterator();
365             while (i.hasNext()) {
366                 App entry = i.next();
367                 if (entry.id == id) {
368                     return entry;
369                 }
370             }
371         }
372         Log.e(TAG, "Context not found for ID " + id);
373         return null;
374     }
375 
376     /**
377      * Get an application context by UUID.
378      */
getByUuid(UUID uuid)379     App getByUuid(UUID uuid) {
380         synchronized (mAppsLock) {
381             Iterator<App> i = mApps.iterator();
382             while (i.hasNext()) {
383                 App entry = i.next();
384                 if (entry.uuid.equals(uuid)) {
385                     return entry;
386                 }
387             }
388         }
389         Log.e(TAG, "Context not found for UUID " + uuid);
390         return null;
391     }
392 
393     /**
394      * Get an application context by the calling Apps name.
395      */
getByName(String name)396     App getByName(String name) {
397         synchronized (mAppsLock) {
398             Iterator<App> i = mApps.iterator();
399             while (i.hasNext()) {
400                 App entry = i.next();
401                 if (entry.name.equals(name)) {
402                     return entry;
403                 }
404             }
405         }
406         Log.e(TAG, "Context not found for name " + name);
407         return null;
408     }
409 
410     /**
411      * Get an application context by the context info object.
412      */
getByContextInfo(T contextInfo)413     App getByContextInfo(T contextInfo) {
414         synchronized (mAppsLock) {
415             Iterator<App> i = mApps.iterator();
416             while (i.hasNext()) {
417                 App entry = i.next();
418                 if (entry.info != null && entry.info.equals(contextInfo)) {
419                     return entry;
420                 }
421             }
422         }
423         Log.e(TAG, "Context not found for info " + contextInfo);
424         return null;
425     }
426 
427     /**
428      * Get Logging info by ID
429      */
getAppScanStatsById(int id)430     AppScanStats getAppScanStatsById(int id) {
431         App temp = getById(id);
432         if (temp != null) {
433             return temp.appScanStats;
434         }
435         return null;
436     }
437 
438     /**
439      * Get Logging info by application UID
440      */
getAppScanStatsByUid(int uid)441     AppScanStats getAppScanStatsByUid(int uid) {
442         return mAppScanStats.get(uid);
443     }
444 
445     /**
446      * Remove the context for a given application ID.
447      */
removeAppAdvertiseStats(int id)448     void removeAppAdvertiseStats(int id) {
449         synchronized (this) {
450             mAppAdvertiseStats.remove(id);
451         }
452     }
453 
454     /**
455      * Get Logging info by ID
456      */
getAppAdvertiseStatsById(int id)457     AppAdvertiseStats getAppAdvertiseStatsById(int id) {
458         synchronized (this) {
459             return mAppAdvertiseStats.get(id);
460         }
461     }
462 
463     /**
464      * update the advertiser ID by the regiseter ID
465      */
setAdvertiserIdByRegId(int regId, int advertiserId)466     void setAdvertiserIdByRegId(int regId, int advertiserId) {
467         synchronized (this) {
468             AppAdvertiseStats stats = mAppAdvertiseStats.get(regId);
469             if (stats == null) {
470                 return;
471             }
472             stats.setId(advertiserId);
473             mAppAdvertiseStats.remove(regId);
474             mAppAdvertiseStats.put(advertiserId, stats);
475         }
476     }
477 
recordAdvertiseStart(int id, AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents)478     void recordAdvertiseStart(int id, AdvertisingSetParameters parameters,
479             AdvertiseData advertiseData, AdvertiseData scanResponse,
480             PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
481             int duration, int maxExtAdvEvents) {
482         synchronized (this) {
483             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
484             if (stats == null) {
485                 return;
486             }
487             stats.recordAdvertiseStart(parameters, advertiseData, scanResponse,
488                     periodicParameters, periodicData, duration, maxExtAdvEvents);
489             int advertiseInstanceCount = mAppAdvertiseStats.size();
490             Log.d(TAG, "advertiseInstanceCount is " + advertiseInstanceCount);
491             AppAdvertiseStats.recordAdvertiseInstanceCount(advertiseInstanceCount);
492         }
493     }
494 
recordAdvertiseStop(int id)495     void recordAdvertiseStop(int id) {
496         synchronized (this) {
497             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
498             if (stats == null) {
499                 return;
500             }
501             stats.recordAdvertiseStop();
502             mAppAdvertiseStats.remove(id);
503             mLastAdvertises.add(stats);
504         }
505     }
506 
enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents)507     void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) {
508         synchronized (this) {
509             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
510             if (stats == null) {
511                 return;
512             }
513             stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents);
514         }
515     }
516 
setAdvertisingData(int id, AdvertiseData data)517     void setAdvertisingData(int id, AdvertiseData data) {
518         synchronized (this) {
519             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
520             if (stats == null) {
521                 return;
522             }
523             stats.setAdvertisingData(data);
524         }
525     }
526 
setScanResponseData(int id, AdvertiseData data)527     void setScanResponseData(int id, AdvertiseData data) {
528         synchronized (this) {
529             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
530             if (stats == null) {
531                 return;
532             }
533             stats.setScanResponseData(data);
534         }
535     }
536 
setAdvertisingParameters(int id, AdvertisingSetParameters parameters)537     void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) {
538         synchronized (this) {
539             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
540             if (stats == null) {
541                 return;
542             }
543             stats.setAdvertisingParameters(parameters);
544         }
545     }
546 
setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters)547     void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) {
548         synchronized (this) {
549             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
550             if (stats == null) {
551                 return;
552             }
553             stats.setPeriodicAdvertisingParameters(parameters);
554         }
555     }
556 
setPeriodicAdvertisingData(int id, AdvertiseData data)557     void setPeriodicAdvertisingData(int id, AdvertiseData data) {
558         synchronized (this) {
559             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
560             if (stats == null) {
561                 return;
562             }
563             stats.setPeriodicAdvertisingData(data);
564         }
565     }
566 
onPeriodicAdvertiseEnabled(int id, boolean enable)567     void onPeriodicAdvertiseEnabled(int id, boolean enable) {
568         synchronized (this) {
569             AppAdvertiseStats stats = mAppAdvertiseStats.get(id);
570             if (stats == null) {
571                 return;
572             }
573             stats.onPeriodicAdvertiseEnabled(enable);
574         }
575     }
576 
577     /**
578      * Get the device addresses for all connected devices
579      */
getConnectedDevices()580     Set<String> getConnectedDevices() {
581         Set<String> addresses = new HashSet<String>();
582         synchronized (mConnectionsLock) {
583             Iterator<Connection> i = mConnections.iterator();
584             while (i.hasNext()) {
585                 Connection connection = i.next();
586                 addresses.add(connection.address);
587             }
588         }
589         return addresses;
590     }
591 
592     /**
593      * Get an application context by a connection ID.
594      */
getByConnId(int connId)595     App getByConnId(int connId) {
596         int appId = -1;
597         synchronized (mConnectionsLock) {
598             Iterator<Connection> ii = mConnections.iterator();
599             while (ii.hasNext()) {
600                 Connection connection = ii.next();
601                 if (connection.connId == connId) {
602                     appId = connection.appId;
603                     break;
604                 }
605             }
606         }
607         if (appId >= 0) {
608             return getById(appId);
609         }
610         return null;
611     }
612 
613     /**
614      * Returns a connection ID for a given device address.
615      */
connIdByAddress(int id, String address)616     Integer connIdByAddress(int id, String address) {
617         App entry = getById(id);
618         if (entry == null) {
619             return null;
620         }
621         synchronized (mConnectionsLock) {
622             Iterator<Connection> i = mConnections.iterator();
623             while (i.hasNext()) {
624                 Connection connection = i.next();
625                 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
626                     return connection.connId;
627                 }
628             }
629         }
630         return null;
631     }
632 
633     /**
634      * Returns the device address for a given connection ID.
635      */
addressByConnId(int connId)636     String addressByConnId(int connId) {
637         synchronized (mConnectionsLock) {
638             Iterator<Connection> i = mConnections.iterator();
639             while (i.hasNext()) {
640                 Connection connection = i.next();
641                 if (connection.connId == connId) {
642                     return connection.address;
643                 }
644             }
645         }
646         return null;
647     }
648 
getConnectionByApp(int appId)649     List<Connection> getConnectionByApp(int appId) {
650         List<Connection> currentConnections = new ArrayList<Connection>();
651         synchronized (mConnectionsLock) {
652             Iterator<Connection> i = mConnections.iterator();
653             while (i.hasNext()) {
654                 Connection connection = i.next();
655                 if (connection.appId == appId) {
656                     currentConnections.add(connection);
657                 }
658             }
659         }
660         return currentConnections;
661     }
662 
663     /**
664      * Erases all application context entries.
665      */
clear()666     void clear() {
667         synchronized (mAppsLock) {
668             Iterator<App> i = mApps.iterator();
669             while (i.hasNext()) {
670                 App entry = i.next();
671                 entry.unlinkToDeath();
672                 if (entry.appScanStats != null) {
673                     entry.appScanStats.isRegistered = false;
674                 }
675                 i.remove();
676             }
677         }
678 
679         synchronized (mConnectionsLock) {
680             mConnections.clear();
681         }
682 
683         synchronized (this) {
684             mAppAdvertiseStats.clear();
685             mLastAdvertises.clear();
686         }
687     }
688 
689     /**
690      * Returns connect device map with addr and appid
691      */
getConnectedMap()692     Map<Integer, String> getConnectedMap() {
693         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
694         synchronized (mConnectionsLock) {
695             for (Connection conn : mConnections) {
696                 connectedmap.put(conn.appId, conn.address);
697             }
698         }
699         return connectedmap;
700     }
701 
702     /**
703      * Logs debug information.
704      */
dump(StringBuilder sb)705     void dump(StringBuilder sb) {
706         sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
707 
708         Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator();
709         while (it.hasNext()) {
710             Map.Entry<Integer, AppScanStats> entry = it.next();
711 
712             AppScanStats appScanStats = entry.getValue();
713             appScanStats.dumpToString(sb);
714         }
715     }
716 
717     /**
718      * Logs advertiser debug information.
719      */
dumpAdvertiser(StringBuilder sb)720     void dumpAdvertiser(StringBuilder sb) {
721         synchronized (this) {
722             if (!mLastAdvertises.isEmpty()) {
723                 sb.append("\n  last " + mLastAdvertises.size() + " advertising:");
724                 for (AppAdvertiseStats stats : mLastAdvertises) {
725                     AppAdvertiseStats.dumpToString(sb, stats);
726                 }
727                 sb.append("\n");
728             }
729 
730             if (!mAppAdvertiseStats.isEmpty()) {
731                 sb.append("  Total number of ongoing advertising                   : "
732                         + mAppAdvertiseStats.size());
733                 sb.append("\n  Ongoing advertising:");
734                 for (Integer key : mAppAdvertiseStats.keySet()) {
735                     AppAdvertiseStats stats = mAppAdvertiseStats.get(key);
736                     AppAdvertiseStats.dumpToString(sb, stats);
737                 }
738             }
739             sb.append("\n");
740         }
741         Log.d(TAG, sb.toString());
742     }
743 }
744