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