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
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    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, 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     */
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    /**
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     * Remove all connections for a given application ID.
233     */
234    void removeConnectionsByAppId(int appId) {
235        Iterator<Connection> i = mConnections.iterator();
236        while (i.hasNext()) {
237            Connection connection = i.next();
238            if (connection.appId == appId) {
239                i.remove();
240            }
241        }
242    }
243
244    /**
245     * Get an application context by ID.
246     */
247    App getById(int id) {
248        Iterator<App> i = mApps.iterator();
249        while (i.hasNext()) {
250            App entry = i.next();
251            if (entry.id == id) return entry;
252        }
253        Log.e(TAG, "Context not found for ID " + id);
254        return null;
255    }
256
257    /**
258     * Get an application context by UUID.
259     */
260    App getByUuid(UUID uuid) {
261        Iterator<App> i = mApps.iterator();
262        while (i.hasNext()) {
263            App entry = i.next();
264            if (entry.uuid.equals(uuid)) return entry;
265        }
266        Log.e(TAG, "Context not found for UUID " + uuid);
267        return null;
268    }
269
270    /**
271     * Get an application context by the calling Apps name.
272     */
273    App getByName(String name) {
274        Iterator<App> i = mApps.iterator();
275        while (i.hasNext()) {
276            App entry = i.next();
277            if (entry.name.equals(name)) return entry;
278        }
279        Log.e(TAG, "Context not found for name " + name);
280        return null;
281    }
282
283    /**
284     * Get Logging info by ID
285     */
286    AppScanStats getAppScanStatsById(int id) {
287        App temp = getById(id);
288        if (temp != null) {
289            return temp.appScanStats;
290        }
291        return null;
292    }
293
294    /**
295     * Get Logging info by application name
296     */
297    AppScanStats getAppScanStatsByName(String name) {
298        return mAppScanStats.get(name);
299    }
300
301    /**
302     * Get the device addresses for all connected devices
303     */
304    Set<String> getConnectedDevices() {
305        Set<String> addresses = new HashSet<String>();
306        Iterator<Connection> i = mConnections.iterator();
307        while (i.hasNext()) {
308            Connection connection = i.next();
309            addresses.add(connection.address);
310        }
311        return addresses;
312    }
313
314    /**
315     * Get an application context by a connection ID.
316     */
317    App getByConnId(int connId) {
318        Iterator<Connection> ii = mConnections.iterator();
319        while (ii.hasNext()) {
320            Connection connection = ii.next();
321            if (connection.connId == connId){
322                return getById(connection.appId);
323            }
324        }
325        return null;
326    }
327
328    /**
329     * Returns a connection ID for a given device address.
330     */
331    Integer connIdByAddress(int id, String address) {
332        App entry = getById(id);
333        if (entry == null) return null;
334
335        Iterator<Connection> i = mConnections.iterator();
336        while (i.hasNext()) {
337            Connection connection = i.next();
338            if (connection.address.equals(address) && connection.appId == id)
339                return connection.connId;
340        }
341        return null;
342    }
343
344    /**
345     * Returns the device address for a given connection ID.
346     */
347    String addressByConnId(int connId) {
348        Iterator<Connection> i = mConnections.iterator();
349        while (i.hasNext()) {
350            Connection connection = i.next();
351            if (connection.connId == connId) return connection.address;
352        }
353        return null;
354    }
355
356    List<Connection> getConnectionByApp(int appId) {
357        List<Connection> currentConnections = new ArrayList<Connection>();
358        Iterator<Connection> i = mConnections.iterator();
359        while (i.hasNext()) {
360            Connection connection = i.next();
361            if (connection.appId == appId)
362                currentConnections.add(connection);
363        }
364        return currentConnections;
365    }
366
367    /**
368     * Erases all application context entries.
369     */
370    void clear() {
371        synchronized (mApps) {
372            Iterator<App> i = mApps.iterator();
373            while (i.hasNext()) {
374                App entry = i.next();
375                entry.unlinkToDeath();
376                entry.appScanStats.isRegistered = false;
377                i.remove();
378            }
379        }
380
381        synchronized (mConnections) {
382            mConnections.clear();
383        }
384    }
385
386    /**
387     * Returns connect device map with addr and appid
388     */
389    Map<Integer, String> getConnectedMap(){
390        Map<Integer, String> connectedmap = new HashMap<Integer, String>();
391        for(Connection conn: mConnections){
392            connectedmap.put(conn.appId, conn.address);
393        }
394        return connectedmap;
395    }
396
397    /**
398     * Logs debug information.
399     */
400    void dump(StringBuilder sb) {
401        sb.append("  Entries: " + mAppScanStats.size() + "\n\n");
402
403        Iterator<Map.Entry<String, AppScanStats>> it = mAppScanStats.entrySet().iterator();
404        while (it.hasNext()) {
405            Map.Entry<String, AppScanStats> entry = it.next();
406
407            String name = entry.getKey();
408            AppScanStats appScanStats = entry.getValue();
409            appScanStats.dumpToString(sb);
410        }
411    }
412}
413