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.os.Binder;
19 import android.os.IBinder;
20 import android.os.IInterface;
21 import android.os.RemoteException;
22 import android.os.SystemClock;
23 import android.os.UserHandle;
24 import android.os.WorkSource;
25 import android.util.Log;
26 
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.NoSuchElementException;
34 import java.util.Set;
35 import java.util.UUID;
36 
37 /**
38  * Helper class that keeps track of registered GATT applications.
39  * This class manages application callbacks and keeps track of GATT connections.
40  * @hide
41  */
42 /*package*/ class ContextMap<C, T> {
43     private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
44 
45     /**
46      * Connection class helps map connection IDs to device addresses.
47      */
48     class Connection {
49         public int connId;
50         public String address;
51         public int appId;
52         public long startTime;
53 
Connection(int connId, String address, int appId)54         Connection(int connId, String address, int appId) {
55             this.connId = connId;
56             this.address = address;
57             this.appId = appId;
58             this.startTime = SystemClock.elapsedRealtime();
59         }
60     }
61 
62     /**
63      * Application entry mapping UUIDs to appIDs and callbacks.
64      */
65     class App {
66         /** The UUID of the application */
67         public UUID uuid;
68 
69         /** The id of the application */
70         public int id;
71 
72         /** The package name of the application */
73         public String name;
74 
75         /** Statistics for this app */
76         public AppScanStats appScanStats;
77 
78         /** Application callbacks */
79         public C callback;
80 
81         /** Context information */
82         public T info;
83         /** Death receipient */
84         private IBinder.DeathRecipient mDeathRecipient;
85 
86         /** Flag to signal that transport is congested */
87         public Boolean isCongested = false;
88 
89         /** Whether the calling app has location permission */
90         boolean hasLocationPermission;
91 
92         /** Whether the calling app has bluetooth privileged permission */
93         boolean hasBluetoothPrivilegedPermission;
94 
95         /** The user handle of the app that started the scan */
96         UserHandle mUserHandle;
97 
98         /** Whether the calling app is targeting Q or better */
99         boolean mIsQApp;
100 
101         /** Whether the calling app has the network settings permission */
102         boolean mHasNetworkSettingsPermission;
103 
104         /** Whether the calling app has the network setup wizard permission */
105         boolean mHasNetworkSetupWizardPermission;
106 
107         /** Whether the calling app has the network setup wizard permission */
108         boolean mHasScanWithoutLocationPermission;
109 
110         /** Whether the calling app has disavowed the use of bluetooth for location */
111         boolean mHasDisavowedLocation;
112 
113         boolean mEligibleForSanitizedExposureNotification;
114 
115         public List<String> mAssociatedDevices;
116 
117         /** Internal callback info queue, waiting to be send on congestion clear */
118         private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
119 
120         /**
121          * Creates a new app context.
122          */
App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)123         App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) {
124             this.uuid = uuid;
125             this.callback = callback;
126             this.info = info;
127             this.name = name;
128             this.appScanStats = appScanStats;
129         }
130 
131         /**
132          * Link death recipient
133          */
linkToDeath(IBinder.DeathRecipient deathRecipient)134         void linkToDeath(IBinder.DeathRecipient deathRecipient) {
135             // It might not be a binder object
136             if (callback == null) {
137                 return;
138             }
139             try {
140                 IBinder binder = ((IInterface) callback).asBinder();
141                 binder.linkToDeath(deathRecipient, 0);
142                 mDeathRecipient = deathRecipient;
143             } catch (RemoteException e) {
144                 Log.e(TAG, "Unable to link deathRecipient for app id " + id);
145             }
146         }
147 
148         /**
149          * Unlink death recipient
150          */
unlinkToDeath()151         void unlinkToDeath() {
152             if (mDeathRecipient != null) {
153                 try {
154                     IBinder binder = ((IInterface) callback).asBinder();
155                     binder.unlinkToDeath(mDeathRecipient, 0);
156                 } catch (NoSuchElementException e) {
157                     Log.e(TAG, "Unable to unlink deathRecipient for app id " + id);
158                 }
159             }
160         }
161 
queueCallback(CallbackInfo callbackInfo)162         void queueCallback(CallbackInfo callbackInfo) {
163             mCongestionQueue.add(callbackInfo);
164         }
165 
popQueuedCallback()166         CallbackInfo popQueuedCallback() {
167             if (mCongestionQueue.size() == 0) {
168                 return null;
169             }
170             return mCongestionQueue.remove(0);
171         }
172     }
173 
174     /** Our internal application list */
175     private List<App> mApps = new ArrayList<App>();
176 
177     /** Internal map to keep track of logging information by app name */
178     private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
179 
180     /** Internal list of connected devices **/
181     private Set<Connection> mConnections = new HashSet<Connection>();
182 
183     /**
184      * Add an entry to the application context list.
185      */
add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)186     App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) {
187         int appUid = Binder.getCallingUid();
188         String appName = service.getPackageManager().getNameForUid(appUid);
189         if (appName == null) {
190             // Assign an app name if one isn't found
191             appName = "Unknown App (UID: " + appUid + ")";
192         }
193         synchronized (mApps) {
194             AppScanStats appScanStats = mAppScanStats.get(appUid);
195             if (appScanStats == null) {
196                 appScanStats = new AppScanStats(appName, workSource, this, service);
197                 mAppScanStats.put(appUid, appScanStats);
198             }
199             App app = new App(uuid, callback, info, appName, appScanStats);
200             mApps.add(app);
201             appScanStats.isRegistered = true;
202             return app;
203         }
204     }
205 
206     /**
207      * Remove the context for a given UUID
208      */
remove(UUID uuid)209     void remove(UUID uuid) {
210         synchronized (mApps) {
211             Iterator<App> i = mApps.iterator();
212             while (i.hasNext()) {
213                 App entry = i.next();
214                 if (entry.uuid.equals(uuid)) {
215                     entry.unlinkToDeath();
216                     entry.appScanStats.isRegistered = false;
217                     i.remove();
218                     break;
219                 }
220             }
221         }
222     }
223 
224     /**
225      * Remove the context for a given application ID.
226      */
remove(int id)227     void remove(int id) {
228         boolean find = false;
229         synchronized (mApps) {
230             Iterator<App> i = mApps.iterator();
231             while (i.hasNext()) {
232                 App entry = i.next();
233                 if (entry.id == id) {
234                     find = true;
235                     entry.unlinkToDeath();
236                     entry.appScanStats.isRegistered = false;
237                     i.remove();
238                     break;
239                 }
240             }
241         }
242         if (find) {
243             removeConnectionsByAppId(id);
244         }
245     }
246 
getAllAppsIds()247     List<Integer> getAllAppsIds() {
248         List<Integer> appIds = new ArrayList();
249         synchronized (mApps) {
250             Iterator<App> i = mApps.iterator();
251             while (i.hasNext()) {
252                 App entry = i.next();
253                 appIds.add(entry.id);
254             }
255         }
256         return appIds;
257     }
258 
259     /**
260      * Add a new connection for a given application ID.
261      */
addConnection(int id, int connId, String address)262     void addConnection(int id, int connId, String address) {
263         synchronized (mConnections) {
264             App entry = getById(id);
265             if (entry != null) {
266                 mConnections.add(new Connection(connId, address, id));
267             }
268         }
269     }
270 
271     /**
272      * Remove a connection with the given ID.
273      */
removeConnection(int id, int connId)274     void removeConnection(int id, int connId) {
275         synchronized (mConnections) {
276             Iterator<Connection> i = mConnections.iterator();
277             while (i.hasNext()) {
278                 Connection connection = i.next();
279                 if (connection.connId == connId) {
280                     i.remove();
281                     break;
282                 }
283             }
284         }
285     }
286 
287     /**
288      * Remove all connections for a given application ID.
289      */
removeConnectionsByAppId(int appId)290     void removeConnectionsByAppId(int appId) {
291         synchronized (mConnections) {
292             Iterator<Connection> i = mConnections.iterator();
293             while (i.hasNext()) {
294                 Connection connection = i.next();
295                 if (connection.appId == appId) {
296                     i.remove();
297                 }
298             }
299         }
300     }
301 
302     /**
303      * Get an application context by ID.
304      */
getById(int id)305     App getById(int id) {
306         synchronized (mApps) {
307             Iterator<App> i = mApps.iterator();
308             while (i.hasNext()) {
309                 App entry = i.next();
310                 if (entry.id == id) {
311                     return entry;
312                 }
313             }
314         }
315         Log.e(TAG, "Context not found for ID " + id);
316         return null;
317     }
318 
319     /**
320      * Get an application context by UUID.
321      */
getByUuid(UUID uuid)322     App getByUuid(UUID uuid) {
323         synchronized (mApps) {
324             Iterator<App> i = mApps.iterator();
325             while (i.hasNext()) {
326                 App entry = i.next();
327                 if (entry.uuid.equals(uuid)) {
328                     return entry;
329                 }
330             }
331         }
332         Log.e(TAG, "Context not found for UUID " + uuid);
333         return null;
334     }
335 
336     /**
337      * Get an application context by the calling Apps name.
338      */
getByName(String name)339     App getByName(String name) {
340         synchronized (mApps) {
341             Iterator<App> i = mApps.iterator();
342             while (i.hasNext()) {
343                 App entry = i.next();
344                 if (entry.name.equals(name)) {
345                     return entry;
346                 }
347             }
348         }
349         Log.e(TAG, "Context not found for name " + name);
350         return null;
351     }
352 
353     /**
354      * Get an application context by the context info object.
355      */
getByContextInfo(T contextInfo)356     App getByContextInfo(T contextInfo) {
357         synchronized (mApps) {
358             Iterator<App> i = mApps.iterator();
359             while (i.hasNext()) {
360                 App entry = i.next();
361                 if (entry.info != null && entry.info.equals(contextInfo)) {
362                     return entry;
363                 }
364             }
365         }
366         Log.e(TAG, "Context not found for info " + contextInfo);
367         return null;
368     }
369 
370     /**
371      * Get Logging info by ID
372      */
getAppScanStatsById(int id)373     AppScanStats getAppScanStatsById(int id) {
374         App temp = getById(id);
375         if (temp != null) {
376             return temp.appScanStats;
377         }
378         return null;
379     }
380 
381     /**
382      * Get Logging info by application UID
383      */
getAppScanStatsByUid(int uid)384     AppScanStats getAppScanStatsByUid(int uid) {
385         return mAppScanStats.get(uid);
386     }
387 
388     /**
389      * Get the device addresses for all connected devices
390      */
getConnectedDevices()391     Set<String> getConnectedDevices() {
392         Set<String> addresses = new HashSet<String>();
393         synchronized (mConnections) {
394             Iterator<Connection> i = mConnections.iterator();
395             while (i.hasNext()) {
396                 Connection connection = i.next();
397                 addresses.add(connection.address);
398             }
399         }
400         return addresses;
401     }
402 
403     /**
404      * Get an application context by a connection ID.
405      */
getByConnId(int connId)406     App getByConnId(int connId) {
407         int appId = -1;
408         synchronized (mConnections) {
409             Iterator<Connection> ii = mConnections.iterator();
410             while (ii.hasNext()) {
411                 Connection connection = ii.next();
412                 if (connection.connId == connId) {
413                     appId = connection.appId;
414                     break;
415                 }
416             }
417         }
418         if (appId >= 0) {
419             return getById(appId);
420         }
421         return null;
422     }
423 
424     /**
425      * Returns a connection ID for a given device address.
426      */
connIdByAddress(int id, String address)427     Integer connIdByAddress(int id, String address) {
428         App entry = getById(id);
429         if (entry == null) {
430             return null;
431         }
432         synchronized (mConnections) {
433             Iterator<Connection> i = mConnections.iterator();
434             while (i.hasNext()) {
435                 Connection connection = i.next();
436                 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
437                     return connection.connId;
438                 }
439             }
440         }
441         return null;
442     }
443 
444     /**
445      * Returns the device address for a given connection ID.
446      */
addressByConnId(int connId)447     String addressByConnId(int connId) {
448         synchronized (mConnections) {
449             Iterator<Connection> i = mConnections.iterator();
450             while (i.hasNext()) {
451                 Connection connection = i.next();
452                 if (connection.connId == connId) {
453                     return connection.address;
454                 }
455             }
456         }
457         return null;
458     }
459 
getConnectionByApp(int appId)460     List<Connection> getConnectionByApp(int appId) {
461         List<Connection> currentConnections = new ArrayList<Connection>();
462         synchronized (mConnections) {
463             Iterator<Connection> i = mConnections.iterator();
464             while (i.hasNext()) {
465                 Connection connection = i.next();
466                 if (connection.appId == appId) {
467                     currentConnections.add(connection);
468                 }
469             }
470         }
471         return currentConnections;
472     }
473 
474     /**
475      * Erases all application context entries.
476      */
clear()477     void clear() {
478         synchronized (mApps) {
479             Iterator<App> i = mApps.iterator();
480             while (i.hasNext()) {
481                 App entry = i.next();
482                 entry.unlinkToDeath();
483                 entry.appScanStats.isRegistered = false;
484                 i.remove();
485             }
486         }
487 
488         synchronized (mConnections) {
489             mConnections.clear();
490         }
491     }
492 
493     /**
494      * Returns connect device map with addr and appid
495      */
getConnectedMap()496     Map<Integer, String> getConnectedMap() {
497         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
498         synchronized (mConnections) {
499             for (Connection conn : mConnections) {
500                 connectedmap.put(conn.appId, conn.address);
501             }
502         }
503         return connectedmap;
504     }
505 
506     /**
507      * Logs debug information.
508      */
dump(StringBuilder sb)509     void dump(StringBuilder sb) {
510         sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
511 
512         Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator();
513         while (it.hasNext()) {
514             Map.Entry<Integer, AppScanStats> entry = it.next();
515 
516             AppScanStats appScanStats = entry.getValue();
517             appScanStats.dumpToString(sb);
518         }
519     }
520 }
521