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         boolean mEligibleForSanitizedExposureNotification;
111 
112         public List<String> mAssociatedDevices;
113 
114         /** Internal callback info queue, waiting to be send on congestion clear */
115         private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
116 
117         /**
118          * Creates a new app context.
119          */
App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)120         App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) {
121             this.uuid = uuid;
122             this.callback = callback;
123             this.info = info;
124             this.name = name;
125             this.appScanStats = appScanStats;
126         }
127 
128         /**
129          * Link death recipient
130          */
linkToDeath(IBinder.DeathRecipient deathRecipient)131         void linkToDeath(IBinder.DeathRecipient deathRecipient) {
132             // It might not be a binder object
133             if (callback == null) {
134                 return;
135             }
136             try {
137                 IBinder binder = ((IInterface) callback).asBinder();
138                 binder.linkToDeath(deathRecipient, 0);
139                 mDeathRecipient = deathRecipient;
140             } catch (RemoteException e) {
141                 Log.e(TAG, "Unable to link deathRecipient for app id " + id);
142             }
143         }
144 
145         /**
146          * Unlink death recipient
147          */
unlinkToDeath()148         void unlinkToDeath() {
149             if (mDeathRecipient != null) {
150                 try {
151                     IBinder binder = ((IInterface) callback).asBinder();
152                     binder.unlinkToDeath(mDeathRecipient, 0);
153                 } catch (NoSuchElementException e) {
154                     Log.e(TAG, "Unable to unlink deathRecipient for app id " + id);
155                 }
156             }
157         }
158 
queueCallback(CallbackInfo callbackInfo)159         void queueCallback(CallbackInfo callbackInfo) {
160             mCongestionQueue.add(callbackInfo);
161         }
162 
popQueuedCallback()163         CallbackInfo popQueuedCallback() {
164             if (mCongestionQueue.size() == 0) {
165                 return null;
166             }
167             return mCongestionQueue.remove(0);
168         }
169     }
170 
171     /** Our internal application list */
172     private List<App> mApps = new ArrayList<App>();
173 
174     /** Internal map to keep track of logging information by app name */
175     HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
176 
177     /** Internal list of connected devices **/
178     Set<Connection> mConnections = new HashSet<Connection>();
179 
180     /**
181      * Add an entry to the application context list.
182      */
add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)183     App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) {
184         int appUid = Binder.getCallingUid();
185         String appName = service.getPackageManager().getNameForUid(appUid);
186         if (appName == null) {
187             // Assign an app name if one isn't found
188             appName = "Unknown App (UID: " + appUid + ")";
189         }
190         synchronized (mApps) {
191             AppScanStats appScanStats = mAppScanStats.get(appUid);
192             if (appScanStats == null) {
193                 appScanStats = new AppScanStats(appName, workSource, this, service);
194                 mAppScanStats.put(appUid, appScanStats);
195             }
196             App app = new App(uuid, callback, info, appName, appScanStats);
197             mApps.add(app);
198             appScanStats.isRegistered = true;
199             return app;
200         }
201     }
202 
203     /**
204      * Remove the context for a given UUID
205      */
remove(UUID uuid)206     void remove(UUID uuid) {
207         synchronized (mApps) {
208             Iterator<App> i = mApps.iterator();
209             while (i.hasNext()) {
210                 App entry = i.next();
211                 if (entry.uuid.equals(uuid)) {
212                     entry.unlinkToDeath();
213                     entry.appScanStats.isRegistered = false;
214                     i.remove();
215                     break;
216                 }
217             }
218         }
219     }
220 
221     /**
222      * Remove the context for a given application ID.
223      */
remove(int id)224     void remove(int id) {
225         synchronized (mApps) {
226             Iterator<App> i = mApps.iterator();
227             while (i.hasNext()) {
228                 App entry = i.next();
229                 if (entry.id == id) {
230                     removeConnectionsByAppId(id);
231                     entry.unlinkToDeath();
232                     entry.appScanStats.isRegistered = false;
233                     i.remove();
234                     break;
235                 }
236             }
237         }
238     }
239 
getAllAppsIds()240     List<Integer> getAllAppsIds() {
241         List<Integer> appIds = new ArrayList();
242         synchronized (mApps) {
243             Iterator<App> i = mApps.iterator();
244             while (i.hasNext()) {
245                 App entry = i.next();
246                 appIds.add(entry.id);
247             }
248         }
249         return appIds;
250     }
251 
252     /**
253      * Add a new connection for a given application ID.
254      */
addConnection(int id, int connId, String address)255     void addConnection(int id, int connId, String address) {
256         synchronized (mConnections) {
257             App entry = getById(id);
258             if (entry != null) {
259                 mConnections.add(new Connection(connId, address, id));
260             }
261         }
262     }
263 
264     /**
265      * Remove a connection with the given ID.
266      */
removeConnection(int id, int connId)267     void removeConnection(int id, int connId) {
268         synchronized (mConnections) {
269             Iterator<Connection> i = mConnections.iterator();
270             while (i.hasNext()) {
271                 Connection connection = i.next();
272                 if (connection.connId == connId) {
273                     i.remove();
274                     break;
275                 }
276             }
277         }
278     }
279 
280     /**
281      * Remove all connections for a given application ID.
282      */
removeConnectionsByAppId(int appId)283     void removeConnectionsByAppId(int appId) {
284         Iterator<Connection> i = mConnections.iterator();
285         while (i.hasNext()) {
286             Connection connection = i.next();
287             if (connection.appId == appId) {
288                 i.remove();
289             }
290         }
291     }
292 
293     /**
294      * Get an application context by ID.
295      */
getById(int id)296     App getById(int id) {
297         synchronized (mApps) {
298             Iterator<App> i = mApps.iterator();
299             while (i.hasNext()) {
300                 App entry = i.next();
301                 if (entry.id == id) {
302                     return entry;
303                 }
304             }
305         }
306         Log.e(TAG, "Context not found for ID " + id);
307         return null;
308     }
309 
310     /**
311      * Get an application context by UUID.
312      */
getByUuid(UUID uuid)313     App getByUuid(UUID uuid) {
314         synchronized (mApps) {
315             Iterator<App> i = mApps.iterator();
316             while (i.hasNext()) {
317                 App entry = i.next();
318                 if (entry.uuid.equals(uuid)) {
319                     return entry;
320                 }
321             }
322         }
323         Log.e(TAG, "Context not found for UUID " + uuid);
324         return null;
325     }
326 
327     /**
328      * Get an application context by the calling Apps name.
329      */
getByName(String name)330     App getByName(String name) {
331         synchronized (mApps) {
332             Iterator<App> i = mApps.iterator();
333             while (i.hasNext()) {
334                 App entry = i.next();
335                 if (entry.name.equals(name)) {
336                     return entry;
337                 }
338             }
339         }
340         Log.e(TAG, "Context not found for name " + name);
341         return null;
342     }
343 
344     /**
345      * Get an application context by the context info object.
346      */
getByContextInfo(T contextInfo)347     App getByContextInfo(T contextInfo) {
348         synchronized (mApps) {
349             Iterator<App> i = mApps.iterator();
350             while (i.hasNext()) {
351                 App entry = i.next();
352                 if (entry.info != null && entry.info.equals(contextInfo)) {
353                     return entry;
354                 }
355             }
356         }
357         Log.e(TAG, "Context not found for info " + contextInfo);
358         return null;
359     }
360 
361     /**
362      * Get Logging info by ID
363      */
getAppScanStatsById(int id)364     AppScanStats getAppScanStatsById(int id) {
365         App temp = getById(id);
366         if (temp != null) {
367             return temp.appScanStats;
368         }
369         return null;
370     }
371 
372     /**
373      * Get Logging info by application UID
374      */
getAppScanStatsByUid(int uid)375     AppScanStats getAppScanStatsByUid(int uid) {
376         return mAppScanStats.get(uid);
377     }
378 
379     /**
380      * Get the device addresses for all connected devices
381      */
getConnectedDevices()382     Set<String> getConnectedDevices() {
383         Set<String> addresses = new HashSet<String>();
384         Iterator<Connection> i = mConnections.iterator();
385         while (i.hasNext()) {
386             Connection connection = i.next();
387             addresses.add(connection.address);
388         }
389         return addresses;
390     }
391 
392     /**
393      * Get an application context by a connection ID.
394      */
getByConnId(int connId)395     App getByConnId(int connId) {
396         Iterator<Connection> ii = mConnections.iterator();
397         while (ii.hasNext()) {
398             Connection connection = ii.next();
399             if (connection.connId == connId) {
400                 return getById(connection.appId);
401             }
402         }
403         return null;
404     }
405 
406     /**
407      * Returns a connection ID for a given device address.
408      */
connIdByAddress(int id, String address)409     Integer connIdByAddress(int id, String address) {
410         App entry = getById(id);
411         if (entry == null) {
412             return null;
413         }
414 
415         Iterator<Connection> i = mConnections.iterator();
416         while (i.hasNext()) {
417             Connection connection = i.next();
418             if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
419                 return connection.connId;
420             }
421         }
422         return null;
423     }
424 
425     /**
426      * Returns the device address for a given connection ID.
427      */
addressByConnId(int connId)428     String addressByConnId(int connId) {
429         Iterator<Connection> i = mConnections.iterator();
430         while (i.hasNext()) {
431             Connection connection = i.next();
432             if (connection.connId == connId) {
433                 return connection.address;
434             }
435         }
436         return null;
437     }
438 
getConnectionByApp(int appId)439     List<Connection> getConnectionByApp(int appId) {
440         List<Connection> currentConnections = new ArrayList<Connection>();
441         Iterator<Connection> i = mConnections.iterator();
442         while (i.hasNext()) {
443             Connection connection = i.next();
444             if (connection.appId == appId) {
445                 currentConnections.add(connection);
446             }
447         }
448         return currentConnections;
449     }
450 
451     /**
452      * Erases all application context entries.
453      */
clear()454     void clear() {
455         synchronized (mApps) {
456             Iterator<App> i = mApps.iterator();
457             while (i.hasNext()) {
458                 App entry = i.next();
459                 entry.unlinkToDeath();
460                 entry.appScanStats.isRegistered = false;
461                 i.remove();
462             }
463         }
464 
465         synchronized (mConnections) {
466             mConnections.clear();
467         }
468     }
469 
470     /**
471      * Returns connect device map with addr and appid
472      */
getConnectedMap()473     Map<Integer, String> getConnectedMap() {
474         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
475         for (Connection conn : mConnections) {
476             connectedmap.put(conn.appId, conn.address);
477         }
478         return connectedmap;
479     }
480 
481     /**
482      * Logs debug information.
483      */
dump(StringBuilder sb)484     void dump(StringBuilder sb) {
485         sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
486 
487         Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator();
488         while (it.hasNext()) {
489             Map.Entry<Integer, AppScanStats> entry = it.next();
490 
491             AppScanStats appScanStats = entry.getValue();
492             appScanStats.dumpToString(sb);
493         }
494     }
495 }
496