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