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