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.Binder; 19 import android.os.IBinder; 20 import android.os.IBinder.DeathRecipient; 21 import android.os.IInterface; 22 import android.os.RemoteException; 23 import android.os.WorkSource; 24 import android.util.Log; 25 import java.util.ArrayList; 26 import java.util.HashSet; 27 import java.util.Iterator; 28 import java.util.List; 29 import java.util.NoSuchElementException; 30 import java.util.Set; 31 import java.util.UUID; 32 import java.util.HashMap; 33 import java.util.Map; 34 35 import com.android.bluetooth.btservice.BluetoothProto; 36 37 /** 38 * Helper class that keeps track of registered GATT applications. 39 * This class manages application callbacks and keeps track of GATT connections. 40 * @hide 41 */ 42 /*package*/ class ContextMap<C, T> { 43 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; 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 /** Statistics for this app */ 76 AppScanStats appScanStats; 77 78 /** Application callbacks */ 79 C callback; 80 81 /** Context information */ 82 T info; 83 /** Death receipient */ 84 private IBinder.DeathRecipient mDeathRecipient; 85 86 /** Flag to signal that transport is congested */ 87 Boolean isCongested = false; 88 89 /** Internal callback info queue, waiting to be send on congestion clear */ 90 private List<CallbackInfo> congestionQueue = new ArrayList<CallbackInfo>(); 91 92 /** 93 * Creates a new app context. 94 */ 95 App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) { 96 this.uuid = uuid; 97 this.callback = callback; 98 this.info = info; 99 this.name = name; 100 this.appScanStats = appScanStats; 101 } 102 103 /** 104 * Link death recipient 105 */ 106 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 107 // It might not be a binder object 108 if (callback == null) return; 109 try { 110 IBinder binder = ((IInterface)callback).asBinder(); 111 binder.linkToDeath(deathRecipient, 0); 112 mDeathRecipient = deathRecipient; 113 } catch (RemoteException e) { 114 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 115 } 116 } 117 118 /** 119 * Unlink death recipient 120 */ 121 void unlinkToDeath() { 122 if (mDeathRecipient != null) { 123 try { 124 IBinder binder = ((IInterface)callback).asBinder(); 125 binder.unlinkToDeath(mDeathRecipient,0); 126 } catch (NoSuchElementException e) { 127 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 128 } 129 } 130 } 131 132 void queueCallback(CallbackInfo callbackInfo) { 133 congestionQueue.add(callbackInfo); 134 } 135 136 CallbackInfo popQueuedCallback() { 137 if (congestionQueue.size() == 0) return null; 138 return congestionQueue.remove(0); 139 } 140 } 141 142 /** Our internal application list */ 143 private List<App> mApps = new ArrayList<App>(); 144 145 /** Internal map to keep track of logging information by app name */ 146 HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>(); 147 148 /** Internal list of connected devices **/ 149 Set<Connection> mConnections = new HashSet<Connection>(); 150 151 /** 152 * Add an entry to the application context list. 153 */ 154 void add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) { 155 int appUid = Binder.getCallingUid(); 156 String appName = service.getPackageManager().getNameForUid(appUid); 157 if (appName == null) { 158 // Assign an app name if one isn't found 159 appName = "Unknown App (UID: " + appUid + ")"; 160 } 161 synchronized (mApps) { 162 AppScanStats appScanStats = mAppScanStats.get(appUid); 163 if (appScanStats == null) { 164 appScanStats = new AppScanStats(appName, workSource, this, service); 165 mAppScanStats.put(appUid, appScanStats); 166 } 167 mApps.add(new App(uuid, callback, info, appName, appScanStats)); 168 appScanStats.isRegistered = true; 169 } 170 } 171 172 /** 173 * Remove the context for a given UUID 174 */ 175 void remove(UUID uuid) { 176 synchronized (mApps) { 177 Iterator<App> i = mApps.iterator(); 178 while (i.hasNext()) { 179 App entry = i.next(); 180 if (entry.uuid.equals(uuid)) { 181 entry.unlinkToDeath(); 182 entry.appScanStats.isRegistered = false; 183 i.remove(); 184 break; 185 } 186 } 187 } 188 } 189 190 /** 191 * Remove the context for a given application ID. 192 */ 193 void remove(int id) { 194 synchronized (mApps) { 195 Iterator<App> i = mApps.iterator(); 196 while (i.hasNext()) { 197 App entry = i.next(); 198 if (entry.id == id) { 199 removeConnectionsByAppId(id); 200 entry.unlinkToDeath(); 201 entry.appScanStats.isRegistered = false; 202 i.remove(); 203 break; 204 } 205 } 206 } 207 } 208 209 List<Integer> getAllAppsIds() { 210 List<Integer> appIds = new ArrayList(); 211 synchronized (mApps) { 212 Iterator<App> i = mApps.iterator(); 213 while (i.hasNext()) { 214 App entry = i.next(); 215 appIds.add(entry.id); 216 } 217 } 218 return appIds; 219 } 220 221 /** 222 * Add a new connection for a given application ID. 223 */ 224 void addConnection(int id, int connId, String address) { 225 synchronized (mConnections) { 226 App entry = getById(id); 227 if (entry != null) { 228 mConnections.add(new Connection(connId, address, id)); 229 } 230 } 231 } 232 233 /** 234 * Remove a connection with the given ID. 235 */ 236 void removeConnection(int id, int connId) { 237 synchronized (mConnections) { 238 Iterator<Connection> i = mConnections.iterator(); 239 while (i.hasNext()) { 240 Connection connection = i.next(); 241 if (connection.connId == connId) { 242 i.remove(); 243 break; 244 } 245 } 246 } 247 } 248 249 /** 250 * Remove all connections for a given application ID. 251 */ 252 void removeConnectionsByAppId(int appId) { 253 Iterator<Connection> i = mConnections.iterator(); 254 while (i.hasNext()) { 255 Connection connection = i.next(); 256 if (connection.appId == appId) { 257 i.remove(); 258 } 259 } 260 } 261 262 /** 263 * Get an application context by ID. 264 */ 265 App getById(int id) { 266 synchronized (mApps) { 267 Iterator<App> i = mApps.iterator(); 268 while (i.hasNext()) { 269 App entry = i.next(); 270 if (entry.id == id) return entry; 271 } 272 } 273 Log.e(TAG, "Context not found for ID " + id); 274 return null; 275 } 276 277 /** 278 * Get an application context by UUID. 279 */ 280 App getByUuid(UUID uuid) { 281 synchronized (mApps) { 282 Iterator<App> i = mApps.iterator(); 283 while (i.hasNext()) { 284 App entry = i.next(); 285 if (entry.uuid.equals(uuid)) return entry; 286 } 287 } 288 Log.e(TAG, "Context not found for UUID " + uuid); 289 return null; 290 } 291 292 /** 293 * Get an application context by the calling Apps name. 294 */ 295 App getByName(String name) { 296 synchronized (mApps) { 297 Iterator<App> i = mApps.iterator(); 298 while (i.hasNext()) { 299 App entry = i.next(); 300 if (entry.name.equals(name)) return entry; 301 } 302 } 303 Log.e(TAG, "Context not found for name " + name); 304 return null; 305 } 306 307 /** 308 * Get an application context by the context info object. 309 */ 310 App getByContextInfo(T contextInfo) { 311 synchronized (mApps) { 312 Iterator<App> i = mApps.iterator(); 313 while (i.hasNext()) { 314 App entry = i.next(); 315 if (entry.info != null && entry.info.equals(contextInfo)) { 316 return entry; 317 } 318 } 319 } 320 Log.e(TAG, "Context not found for info " + contextInfo); 321 return null; 322 } 323 324 /** 325 * Get Logging info by ID 326 */ 327 AppScanStats getAppScanStatsById(int id) { 328 App temp = getById(id); 329 if (temp != null) { 330 return temp.appScanStats; 331 } 332 return null; 333 } 334 335 /** 336 * Get Logging info by application UID 337 */ 338 AppScanStats getAppScanStatsByUid(int uid) { 339 return mAppScanStats.get(uid); 340 } 341 342 /** 343 * Get the device addresses for all connected devices 344 */ 345 Set<String> getConnectedDevices() { 346 Set<String> addresses = new HashSet<String>(); 347 Iterator<Connection> i = mConnections.iterator(); 348 while (i.hasNext()) { 349 Connection connection = i.next(); 350 addresses.add(connection.address); 351 } 352 return addresses; 353 } 354 355 /** 356 * Get an application context by a connection ID. 357 */ 358 App getByConnId(int connId) { 359 Iterator<Connection> ii = mConnections.iterator(); 360 while (ii.hasNext()) { 361 Connection connection = ii.next(); 362 if (connection.connId == connId){ 363 return getById(connection.appId); 364 } 365 } 366 return null; 367 } 368 369 /** 370 * Returns a connection ID for a given device address. 371 */ 372 Integer connIdByAddress(int id, String address) { 373 App entry = getById(id); 374 if (entry == null) return null; 375 376 Iterator<Connection> i = mConnections.iterator(); 377 while (i.hasNext()) { 378 Connection connection = i.next(); 379 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) 380 return connection.connId; 381 } 382 return null; 383 } 384 385 /** 386 * Returns the device address for a given connection ID. 387 */ 388 String addressByConnId(int connId) { 389 Iterator<Connection> i = mConnections.iterator(); 390 while (i.hasNext()) { 391 Connection connection = i.next(); 392 if (connection.connId == connId) return connection.address; 393 } 394 return null; 395 } 396 397 List<Connection> getConnectionByApp(int appId) { 398 List<Connection> currentConnections = new ArrayList<Connection>(); 399 Iterator<Connection> i = mConnections.iterator(); 400 while (i.hasNext()) { 401 Connection connection = i.next(); 402 if (connection.appId == appId) 403 currentConnections.add(connection); 404 } 405 return currentConnections; 406 } 407 408 /** 409 * Erases all application context entries. 410 */ 411 void clear() { 412 synchronized (mApps) { 413 Iterator<App> i = mApps.iterator(); 414 while (i.hasNext()) { 415 App entry = i.next(); 416 entry.unlinkToDeath(); 417 entry.appScanStats.isRegistered = false; 418 i.remove(); 419 } 420 } 421 422 synchronized (mConnections) { 423 mConnections.clear(); 424 } 425 } 426 427 /** 428 * Returns connect device map with addr and appid 429 */ 430 Map<Integer, String> getConnectedMap(){ 431 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 432 for(Connection conn: mConnections){ 433 connectedmap.put(conn.appId, conn.address); 434 } 435 return connectedmap; 436 } 437 438 /** 439 * Logs debug information. 440 */ 441 void dump(StringBuilder sb) { 442 sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); 443 444 Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator(); 445 while (it.hasNext()) { 446 Map.Entry<Integer, AppScanStats> entry = it.next(); 447 448 AppScanStats appScanStats = entry.getValue(); 449 appScanStats.dumpToString(sb); 450 } 451 } 452 } 453