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 */ 16 package com.android.bluetooth.gatt; 17 18 import android.os.IBinder; 19 import android.os.IBinder.DeathRecipient; 20 import android.os.IInterface; 21 import android.os.RemoteException; 22 import android.util.Log; 23 import java.util.ArrayList; 24 import java.util.HashSet; 25 import java.util.Iterator; 26 import java.util.List; 27 import java.util.NoSuchElementException; 28 import java.util.Set; 29 import 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 /** Flag to signal that transport is congested */ 71 Boolean isCongested = false; 72 73 /** Internal callback info queue, waiting to be send on congestion clear */ 74 private List<CallbackInfo> congestionQueue = new ArrayList<CallbackInfo>(); 75 76 /** 77 * Creates a new app context. 78 */ 79 App(UUID uuid, T callback) { 80 this.uuid = uuid; 81 this.callback = callback; 82 } 83 84 /** 85 * Link death recipient 86 */ 87 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 88 try { 89 IBinder binder = ((IInterface)callback).asBinder(); 90 binder.linkToDeath(deathRecipient, 0); 91 mDeathRecipient = deathRecipient; 92 } catch (RemoteException e) { 93 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 94 } 95 } 96 97 /** 98 * Unlink death recipient 99 */ 100 void unlinkToDeath() { 101 if (mDeathRecipient != null) { 102 try { 103 IBinder binder = ((IInterface)callback).asBinder(); 104 binder.unlinkToDeath(mDeathRecipient,0); 105 } catch (NoSuchElementException e) { 106 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 107 } 108 } 109 } 110 111 void queueCallback(CallbackInfo callbackInfo) { 112 congestionQueue.add(callbackInfo); 113 } 114 115 CallbackInfo popQueuedCallback() { 116 if (congestionQueue.size() == 0) return null; 117 return congestionQueue.remove(0); 118 } 119 } 120 121 /** Our internal application list */ 122 List<App> mApps = new ArrayList<App>(); 123 124 /** Internal list of connected devices **/ 125 Set<Connection> mConnections = new HashSet<Connection>(); 126 127 /** 128 * Add an entry to the application context list. 129 */ 130 void add(UUID uuid, T callback) { 131 synchronized (mApps) { 132 mApps.add(new App(uuid, callback)); 133 } 134 } 135 136 /** 137 * Remove the context for a given UUID 138 */ 139 void remove(UUID uuid) { 140 synchronized (mApps) { 141 Iterator<App> i = mApps.iterator(); 142 while(i.hasNext()) { 143 App entry = i.next(); 144 if (entry.uuid.equals(uuid)) { 145 entry.unlinkToDeath(); 146 i.remove(); 147 break; 148 } 149 } 150 } 151 } 152 153 /** 154 * Remove the context for a given application ID. 155 */ 156 void remove(int id) { 157 synchronized (mApps) { 158 Iterator<App> i = mApps.iterator(); 159 while(i.hasNext()) { 160 App entry = i.next(); 161 if (entry.id == id) { 162 entry.unlinkToDeath(); 163 i.remove(); 164 break; 165 } 166 } 167 } 168 } 169 170 /** 171 * Add a new connection for a given application ID. 172 */ 173 void addConnection(int id, int connId, String address) { 174 synchronized (mConnections) { 175 App entry = getById(id); 176 if (entry != null){ 177 mConnections.add(new Connection(connId, address, id)); 178 } 179 } 180 } 181 182 /** 183 * Remove a connection with the given ID. 184 */ 185 void removeConnection(int id, int connId) { 186 synchronized (mConnections) { 187 Iterator<Connection> i = mConnections.iterator(); 188 while(i.hasNext()) { 189 Connection connection = i.next(); 190 if (connection.connId == connId) { 191 i.remove(); 192 break; 193 } 194 } 195 } 196 } 197 198 /** 199 * Get an application context by ID. 200 */ 201 App getById(int id) { 202 Iterator<App> i = mApps.iterator(); 203 while(i.hasNext()) { 204 App entry = i.next(); 205 if (entry.id == id) return entry; 206 } 207 Log.e(TAG, "Context not found for ID " + id); 208 return null; 209 } 210 211 /** 212 * Get an application context by UUID. 213 */ 214 App getByUuid(UUID uuid) { 215 Iterator<App> i = mApps.iterator(); 216 while(i.hasNext()) { 217 App entry = i.next(); 218 if (entry.uuid.equals(uuid)) return entry; 219 } 220 Log.e(TAG, "Context not found for UUID " + uuid); 221 return null; 222 } 223 224 /** 225 * Get the device addresses for all connected devices 226 */ 227 Set<String> getConnectedDevices() { 228 Set<String> addresses = new HashSet<String>(); 229 Iterator<Connection> i = mConnections.iterator(); 230 while(i.hasNext()) { 231 Connection connection = i.next(); 232 addresses.add(connection.address); 233 } 234 return addresses; 235 } 236 237 /** 238 * Get an application context by a connection ID. 239 */ 240 App getByConnId(int connId) { 241 Iterator<Connection> ii = mConnections.iterator(); 242 while(ii.hasNext()) { 243 Connection connection = ii.next(); 244 if (connection.connId == connId){ 245 return getById(connection.appId); 246 } 247 } 248 return null; 249 } 250 251 /** 252 * Returns a connection ID for a given device address. 253 */ 254 Integer connIdByAddress(int id, String address) { 255 App entry = getById(id); 256 if (entry == null) return null; 257 258 Iterator<Connection> i = mConnections.iterator(); 259 while(i.hasNext()) { 260 Connection connection = i.next(); 261 if (connection.address.equals(address) && connection.appId == id) 262 return connection.connId; 263 } 264 return null; 265 } 266 267 /** 268 * Returns the device address for a given connection ID. 269 */ 270 String addressByConnId(int connId) { 271 Iterator<Connection> i = mConnections.iterator(); 272 while(i.hasNext()) { 273 Connection connection = i.next(); 274 if (connection.connId == connId) return connection.address; 275 } 276 return null; 277 } 278 279 List<Connection> getConnectionByApp(int appId) { 280 List<Connection> currentConnections = new ArrayList<Connection>(); 281 Iterator<Connection> i = mConnections.iterator(); 282 while(i.hasNext()) { 283 Connection connection = i.next(); 284 if (connection.appId == appId) 285 currentConnections.add(connection); 286 } 287 return currentConnections; 288 } 289 290 /** 291 * Erases all application context entries. 292 */ 293 void clear() { 294 synchronized (mApps) { 295 Iterator<App> i = mApps.iterator(); 296 while(i.hasNext()) { 297 App entry = i.next(); 298 entry.unlinkToDeath(); 299 i.remove(); 300 } 301 } 302 303 synchronized (mConnections) { 304 mConnections.clear(); 305 } 306 } 307 308 /** 309 * Logs debug information. 310 */ 311 void dump(StringBuilder sb) { 312 sb.append(" Entries: " + mApps.size() + "\n"); 313 314 Iterator<App> i = mApps.iterator(); 315 while(i.hasNext()) { 316 App entry = i.next(); 317 List<Connection> connections = getConnectionByApp(entry.id); 318 319 sb.append("\n Application Id: " + entry.id + "\n"); 320 sb.append(" UUID: " + entry.uuid + "\n"); 321 sb.append(" Connections: " + connections.size() + "\n"); 322 323 Iterator<Connection> ii = connections.iterator(); 324 while(ii.hasNext()) { 325 Connection connection = ii.next(); 326 sb.append(" " + connection.connId + ": " + connection.address + "\n"); 327 } 328 } 329 } 330 } 331