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