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