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