ContextMap.java revision 8e68fb0428b6c4e6ad3aa5aadbb9815e05f723ce
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.os.WorkSource;
24import android.util.Log;
25import java.util.ArrayList;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.List;
29import java.util.NoSuchElementException;
30import java.util.Set;
31import java.util.UUID;
32import java.util.HashMap;
33import java.util.Map;
34
35import 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
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         */
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         */
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         */
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
126        void queueCallback(CallbackInfo callbackInfo) {
127            congestionQueue.add(callbackInfo);
128        }
129
130        CallbackInfo popQueuedCallback() {
131            if (congestionQueue.size() == 0) return null;
132            return congestionQueue.remove(0);
133        }
134    }
135
136    /** Our internal application list */
137    private 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     */
148    void add(UUID uuid, WorkSource workSource, 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, workSource, 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     */
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     */
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                    removeConnectionsByAppId(id);
194                    entry.unlinkToDeath();
195                    entry.appScanStats.isRegistered = false;
196                    i.remove();
197                    break;
198                }
199            }
200        }
201    }
202
203    List<Integer> getAllAppsIds() {
204        List<Integer> appIds = new ArrayList();
205        synchronized (mApps) {
206            Iterator<App> i = mApps.iterator();
207            while (i.hasNext()) {
208                App entry = i.next();
209                appIds.add(entry.id);
210            }
211        }
212        return appIds;
213    }
214
215    /**
216     * Add a new connection for a given application ID.
217     */
218    void addConnection(int id, int connId, String address) {
219        synchronized (mConnections) {
220            App entry = getById(id);
221            if (entry != null) {
222                mConnections.add(new Connection(connId, address, id));
223            }
224        }
225    }
226
227    /**
228     * Remove a connection with the given ID.
229     */
230    void removeConnection(int id, int connId) {
231        synchronized (mConnections) {
232            Iterator<Connection> i = mConnections.iterator();
233            while (i.hasNext()) {
234                Connection connection = i.next();
235                if (connection.connId == connId) {
236                    i.remove();
237                    break;
238                }
239            }
240        }
241    }
242
243    /**
244     * Remove all connections for a given application ID.
245     */
246    void removeConnectionsByAppId(int appId) {
247        Iterator<Connection> i = mConnections.iterator();
248        while (i.hasNext()) {
249            Connection connection = i.next();
250            if (connection.appId == appId) {
251                i.remove();
252            }
253        }
254    }
255
256    /**
257     * Get an application context by ID.
258     */
259    App getById(int id) {
260        synchronized (mApps) {
261            Iterator<App> i = mApps.iterator();
262            while (i.hasNext()) {
263                App entry = i.next();
264                if (entry.id == id) return entry;
265            }
266        }
267        Log.e(TAG, "Context not found for ID " + id);
268        return null;
269    }
270
271    /**
272     * Get an application context by UUID.
273     */
274    App getByUuid(UUID uuid) {
275        synchronized (mApps) {
276            Iterator<App> i = mApps.iterator();
277            while (i.hasNext()) {
278                App entry = i.next();
279                if (entry.uuid.equals(uuid)) return entry;
280            }
281        }
282        Log.e(TAG, "Context not found for UUID " + uuid);
283        return null;
284    }
285
286    /**
287     * Get an application context by the calling Apps name.
288     */
289    App getByName(String name) {
290        synchronized (mApps) {
291            Iterator<App> i = mApps.iterator();
292            while (i.hasNext()) {
293                App entry = i.next();
294                if (entry.name.equals(name)) return entry;
295            }
296        }
297        Log.e(TAG, "Context not found for name " + name);
298        return null;
299    }
300
301    /**
302     * Get Logging info by ID
303     */
304    AppScanStats getAppScanStatsById(int id) {
305        App temp = getById(id);
306        if (temp != null) {
307            return temp.appScanStats;
308        }
309        return null;
310    }
311
312    /**
313     * Get Logging info by application name
314     */
315    AppScanStats getAppScanStatsByName(String name) {
316        return mAppScanStats.get(name);
317    }
318
319    /**
320     * Get the device addresses for all connected devices
321     */
322    Set<String> getConnectedDevices() {
323        Set<String> addresses = new HashSet<String>();
324        Iterator<Connection> i = mConnections.iterator();
325        while (i.hasNext()) {
326            Connection connection = i.next();
327            addresses.add(connection.address);
328        }
329        return addresses;
330    }
331
332    /**
333     * Get an application context by a connection ID.
334     */
335    App getByConnId(int connId) {
336        Iterator<Connection> ii = mConnections.iterator();
337        while (ii.hasNext()) {
338            Connection connection = ii.next();
339            if (connection.connId == connId){
340                return getById(connection.appId);
341            }
342        }
343        return null;
344    }
345
346    /**
347     * Returns a connection ID for a given device address.
348     */
349    Integer connIdByAddress(int id, String address) {
350        App entry = getById(id);
351        if (entry == null) return null;
352
353        Iterator<Connection> i = mConnections.iterator();
354        while (i.hasNext()) {
355            Connection connection = i.next();
356            if (connection.address.equalsIgnoreCase(address) && connection.appId == id)
357                return connection.connId;
358        }
359        return null;
360    }
361
362    /**
363     * Returns the device address for a given connection ID.
364     */
365    String addressByConnId(int connId) {
366        Iterator<Connection> i = mConnections.iterator();
367        while (i.hasNext()) {
368            Connection connection = i.next();
369            if (connection.connId == connId) return connection.address;
370        }
371        return null;
372    }
373
374    List<Connection> getConnectionByApp(int appId) {
375        List<Connection> currentConnections = new ArrayList<Connection>();
376        Iterator<Connection> i = mConnections.iterator();
377        while (i.hasNext()) {
378            Connection connection = i.next();
379            if (connection.appId == appId)
380                currentConnections.add(connection);
381        }
382        return currentConnections;
383    }
384
385    /**
386     * Erases all application context entries.
387     */
388    void clear() {
389        synchronized (mApps) {
390            Iterator<App> i = mApps.iterator();
391            while (i.hasNext()) {
392                App entry = i.next();
393                entry.unlinkToDeath();
394                entry.appScanStats.isRegistered = false;
395                i.remove();
396            }
397        }
398
399        synchronized (mConnections) {
400            mConnections.clear();
401        }
402    }
403
404    /**
405     * Returns connect device map with addr and appid
406     */
407    Map<Integer, String> getConnectedMap(){
408        Map<Integer, String> connectedmap = new HashMap<Integer, String>();
409        for(Connection conn: mConnections){
410            connectedmap.put(conn.appId, conn.address);
411        }
412        return connectedmap;
413    }
414
415    /**
416     * Logs debug information.
417     */
418    void dump(StringBuilder sb) {
419        sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
420
421        Iterator<Map.Entry<String, AppScanStats>> it = mAppScanStats.entrySet().iterator();
422        while (it.hasNext()) {
423            Map.Entry<String, AppScanStats> entry = it.next();
424
425            String name = entry.getKey();
426            AppScanStats appScanStats = entry.getValue();
427            appScanStats.dumpToString(sb);
428        }
429    }
430}
431