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.IBinder;
19import android.os.IBinder.DeathRecipient;
20import android.os.IInterface;
21import android.os.RemoteException;
22import android.util.Log;
23import java.util.ArrayList;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.List;
27import java.util.NoSuchElementException;
28import java.util.Set;
29import java.util.UUID;
30
31/**
32 * Helper class that keeps track of registered GATT applications.
33 * This class manages application callbacks and keeps track of GATT connections.
34 * @hide
35 */
36/*package*/ class ContextMap<T> {
37    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap";
38
39    /**
40     * Connection class helps map connection IDs to device addresses.
41     */
42    class Connection {
43        int connId;
44        String address;
45        int appId;
46
47        Connection(int connId, String address,int appId) {
48            this.connId = connId;
49            this.address = address;
50            this.appId = appId;
51        }
52    }
53
54    /**
55     * Application entry mapping UUIDs to appIDs and callbacks.
56     */
57    class App {
58        /** The UUID of the application */
59        UUID uuid;
60
61        /** The id of the application */
62        int id;
63
64        /** Application callbacks */
65        T callback;
66
67        /** Death receipient */
68        private IBinder.DeathRecipient mDeathRecipient;
69
70        /**
71         * Creates a new app context.
72         */
73        App(UUID uuid, T callback) {
74            this.uuid = uuid;
75            this.callback = callback;
76        }
77
78        /**
79         * Link death recipient
80         */
81        void linkToDeath(IBinder.DeathRecipient deathRecipient) {
82            try {
83                IBinder binder = ((IInterface)callback).asBinder();
84                binder.linkToDeath(deathRecipient, 0);
85                mDeathRecipient = deathRecipient;
86            } catch (RemoteException e) {
87                Log.e(TAG, "Unable to link deathRecipient for app id " + id);
88            }
89        }
90
91        /**
92         * Unlink death recipient
93         */
94        void unlinkToDeath() {
95            if (mDeathRecipient != null) {
96                try {
97                    IBinder binder = ((IInterface)callback).asBinder();
98                    binder.unlinkToDeath(mDeathRecipient,0);
99                } catch (NoSuchElementException e) {
100                    Log.e(TAG, "Unable to unlink deathRecipient for app id " + id);
101                }
102            }
103        }
104    }
105
106    /** Our internal application list */
107    List<App> mApps = new ArrayList<App>();
108
109    /** Internal list of connected devices **/
110    Set<Connection> mConnections = new HashSet<Connection>();
111
112    /**
113     * Add an entry to the application context list.
114     */
115    void add(UUID uuid, T callback) {
116        synchronized (mApps) {
117            mApps.add(new App(uuid, callback));
118        }
119    }
120
121    /**
122     * Remove the context for a given application ID.
123     */
124    void remove(int id) {
125        synchronized (mApps) {
126            Iterator<App> i = mApps.iterator();
127            while(i.hasNext()) {
128                App entry = i.next();
129                if (entry.id == id) {
130                    entry.unlinkToDeath();
131                    i.remove();
132                    break;
133                }
134            }
135        }
136    }
137
138    /**
139     * Add a new connection for a given application ID.
140     */
141    void addConnection(int id, int connId, String address) {
142        synchronized (mConnections) {
143            App entry = getById(id);
144            if (entry != null){
145                mConnections.add(new Connection(connId, address, id));
146            }
147        }
148    }
149
150    /**
151     * Remove a connection with the given ID.
152     */
153    void removeConnection(int id, int connId) {
154        synchronized (mConnections) {
155            Iterator<Connection> i = mConnections.iterator();
156            while(i.hasNext()) {
157                Connection connection = i.next();
158                if (connection.connId == connId) {
159                    i.remove();
160                    break;
161                }
162            }
163        }
164    }
165
166    /**
167     * Get an application context by ID.
168     */
169    App getById(int id) {
170        Iterator<App> i = mApps.iterator();
171        while(i.hasNext()) {
172            App entry = i.next();
173            if (entry.id == id) return entry;
174        }
175        Log.e(TAG, "Context not found for ID " + id);
176        return null;
177    }
178
179    /**
180     * Get an application context by UUID.
181     */
182    App getByUuid(UUID uuid) {
183        Iterator<App> i = mApps.iterator();
184        while(i.hasNext()) {
185            App entry = i.next();
186            if (entry.uuid.equals(uuid)) return entry;
187        }
188        Log.e(TAG, "Context not found for UUID " + uuid);
189        return null;
190    }
191
192    /**
193     * Get the device addresses for all connected devices
194     */
195    Set<String> getConnectedDevices() {
196        Set<String> addresses = new HashSet<String>();
197        Iterator<Connection> i = mConnections.iterator();
198        while(i.hasNext()) {
199            Connection connection = i.next();
200            addresses.add(connection.address);
201        }
202        return addresses;
203    }
204
205    /**
206     * Get an application context by a connection ID.
207     */
208    App getByConnId(int connId) {
209        Iterator<Connection> ii = mConnections.iterator();
210        while(ii.hasNext()) {
211            Connection connection = ii.next();
212            if (connection.connId == connId){
213                return getById(connection.appId);
214            }
215        }
216        return null;
217    }
218
219    /**
220     * Returns a connection ID for a given device address.
221     */
222    Integer connIdByAddress(int id, String address) {
223        App entry = getById(id);
224        if (entry == null) return null;
225
226        Iterator<Connection> i = mConnections.iterator();
227        while(i.hasNext()) {
228            Connection connection = i.next();
229            if (connection.address.equals(address) && connection.appId == id)
230                return connection.connId;
231        }
232        return null;
233    }
234
235    /**
236     * Returns the device address for a given connection ID.
237     */
238    String addressByConnId(int connId) {
239        Iterator<Connection> i = mConnections.iterator();
240        while(i.hasNext()) {
241            Connection connection = i.next();
242            if (connection.connId == connId) return connection.address;
243        }
244        return null;
245    }
246
247    List<Connection> getConnectionByApp(int appId) {
248        List<Connection> currentConnections = new ArrayList<Connection>();
249        Iterator<Connection> i = mConnections.iterator();
250        while(i.hasNext()) {
251            Connection connection = i.next();
252            if (connection.appId == appId)
253                currentConnections.add(connection);
254        }
255        return currentConnections;
256    }
257
258    /**
259     * Erases all application context entries.
260     */
261    void clear() {
262        synchronized (mApps) {
263            Iterator<App> i = mApps.iterator();
264            while(i.hasNext()) {
265                App entry = i.next();
266                entry.unlinkToDeath();
267                i.remove();
268            }
269        }
270
271        synchronized (mConnections) {
272            mConnections.clear();
273        }
274    }
275
276    /**
277     * Logs debug information.
278     */
279    void dump() {
280        StringBuilder b = new StringBuilder();
281        b.append(  "-------------- GATT Context Map ----------------");
282        b.append("\nEntries: " + mApps.size());
283
284        Iterator<App> i = mApps.iterator();
285        while(i.hasNext()) {
286            App entry = i.next();
287            List<Connection> connections = getConnectionByApp(entry.id);
288
289            b.append("\n\nApplication Id: " + entry.id);
290            b.append("\nUUID: " + entry.uuid);
291            b.append("\nConnections: " + connections.size());
292
293            Iterator<Connection> ii = connections.iterator();
294            while(ii.hasNext()) {
295                Connection connection = ii.next();
296                b.append("\n  " + connection.connId + ": " + connection.address);
297            }
298        }
299
300        b.append("\n------------------------------------------------");
301        Log.d(TAG, b.toString());
302    }
303}
304