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