ContextMap.java revision 8a0bc0a61e97f8923cb362e1dbb7ff66ffe51507
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.LinkedList; 29import java.util.List; 30import java.util.NoSuchElementException; 31import java.util.Set; 32import java.util.UUID; 33import java.util.HashMap; 34import java.util.Map; 35 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 * ScanStats class helps keep track of information about scans 46 * on a per application basis. 47 */ 48 class ScanStats { 49 static final int NUM_SCAN_DURATIONS_KEPT = 5; 50 51 int scansStarted = 0; 52 int scansStopped = 0; 53 boolean isScanning = false; 54 boolean isRegistered = false; 55 long minScanTime = Long.MAX_VALUE; 56 long maxScanTime = 0; 57 long totalScanTime = 0; 58 List<Long> lastScans = new ArrayList<Long>(NUM_SCAN_DURATIONS_KEPT + 1); 59 long startTime = 0; 60 long stopTime = 0; 61 62 void startScan() { 63 this.scansStarted++; 64 isScanning = true; 65 startTime = System.currentTimeMillis(); 66 } 67 68 void stopScan() { 69 this.scansStopped++; 70 isScanning = false; 71 stopTime = System.currentTimeMillis(); 72 long currTime = stopTime - startTime; 73 74 minScanTime = Math.min(currTime, minScanTime); 75 maxScanTime = Math.max(currTime, maxScanTime); 76 totalScanTime += currTime; 77 lastScans.add(currTime); 78 if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) { 79 lastScans.remove(0); 80 } 81 } 82 } 83 84 /** 85 * Connection class helps map connection IDs to device addresses. 86 */ 87 class Connection { 88 int connId; 89 String address; 90 int appId; 91 92 Connection(int connId, String address,int appId) { 93 this.connId = connId; 94 this.address = address; 95 this.appId = appId; 96 } 97 } 98 99 /** 100 * Application entry mapping UUIDs to appIDs and callbacks. 101 */ 102 class App { 103 /** The UUID of the application */ 104 UUID uuid; 105 106 /** The id of the application */ 107 int id; 108 109 /** The package name of the application */ 110 String name; 111 112 /** Application callbacks */ 113 T callback; 114 115 /** Death receipient */ 116 private IBinder.DeathRecipient mDeathRecipient; 117 118 /** Flag to signal that transport is congested */ 119 Boolean isCongested = false; 120 121 /** Internal callback info queue, waiting to be send on congestion clear */ 122 private List<CallbackInfo> congestionQueue = new ArrayList<CallbackInfo>(); 123 124 /** 125 * Creates a new app context. 126 */ 127 App(UUID uuid, T callback, String name) { 128 this.uuid = uuid; 129 this.callback = callback; 130 this.name = name; 131 } 132 133 /** 134 * Link death recipient 135 */ 136 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 137 try { 138 IBinder binder = ((IInterface)callback).asBinder(); 139 binder.linkToDeath(deathRecipient, 0); 140 mDeathRecipient = deathRecipient; 141 } catch (RemoteException e) { 142 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 143 } 144 } 145 146 /** 147 * Unlink death recipient 148 */ 149 void unlinkToDeath() { 150 if (mDeathRecipient != null) { 151 try { 152 IBinder binder = ((IInterface)callback).asBinder(); 153 binder.unlinkToDeath(mDeathRecipient,0); 154 } catch (NoSuchElementException e) { 155 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 156 } 157 } 158 } 159 160 void queueCallback(CallbackInfo callbackInfo) { 161 congestionQueue.add(callbackInfo); 162 } 163 164 CallbackInfo popQueuedCallback() { 165 if (congestionQueue.size() == 0) return null; 166 return congestionQueue.remove(0); 167 } 168 } 169 170 /** Our internal application list */ 171 List<App> mApps = new ArrayList<App>(); 172 173 /** Internal map to keep track of logging information by app name */ 174 HashMap<String, ScanStats> mScanStats = new HashMap<String, ScanStats>(); 175 176 /** Internal list of connected devices **/ 177 Set<Connection> mConnections = new HashSet<Connection>(); 178 179 /** 180 * Add an entry to the application context list. 181 */ 182 void add(UUID uuid, T callback, Context context) { 183 String appName = context.getPackageManager().getNameForUid( 184 Binder.getCallingUid()); 185 if (appName == null) { 186 // Assign an app name if one isn't found 187 appName = "Unknown App (UID: " + Binder.getCallingUid() + ")"; 188 } 189 synchronized (mApps) { 190 mApps.add(new App(uuid, callback, appName)); 191 ScanStats scanStats = mScanStats.get(appName); 192 if (scanStats == null) { 193 scanStats = new ScanStats(); 194 mScanStats.put(appName, scanStats); 195 } 196 scanStats.isRegistered = true; 197 } 198 } 199 200 /** 201 * Remove the context for a given UUID 202 */ 203 void remove(UUID uuid) { 204 synchronized (mApps) { 205 Iterator<App> i = mApps.iterator(); 206 while (i.hasNext()) { 207 App entry = i.next(); 208 if (entry.uuid.equals(uuid)) { 209 entry.unlinkToDeath(); 210 mScanStats.get(entry.name).isRegistered = false; 211 i.remove(); 212 break; 213 } 214 } 215 } 216 } 217 218 /** 219 * Remove the context for a given application ID. 220 */ 221 void remove(int id) { 222 synchronized (mApps) { 223 Iterator<App> i = mApps.iterator(); 224 while (i.hasNext()) { 225 App entry = i.next(); 226 if (entry.id == id) { 227 entry.unlinkToDeath(); 228 mScanStats.get(entry.name).isRegistered = false; 229 i.remove(); 230 break; 231 } 232 } 233 } 234 } 235 236 /** 237 * Add a new connection for a given application ID. 238 */ 239 void addConnection(int id, int connId, String address) { 240 synchronized (mConnections) { 241 App entry = getById(id); 242 if (entry != null){ 243 mConnections.add(new Connection(connId, address, id)); 244 } 245 } 246 } 247 248 /** 249 * Remove a connection with the given ID. 250 */ 251 void removeConnection(int id, int connId) { 252 synchronized (mConnections) { 253 Iterator<Connection> i = mConnections.iterator(); 254 while (i.hasNext()) { 255 Connection connection = i.next(); 256 if (connection.connId == connId) { 257 i.remove(); 258 break; 259 } 260 } 261 } 262 } 263 264 /** 265 * Get an application context by ID. 266 */ 267 App getById(int id) { 268 Iterator<App> i = mApps.iterator(); 269 while (i.hasNext()) { 270 App entry = i.next(); 271 if (entry.id == id) return entry; 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 Iterator<App> i = mApps.iterator(); 282 while (i.hasNext()) { 283 App entry = i.next(); 284 if (entry.uuid.equals(uuid)) return entry; 285 } 286 Log.e(TAG, "Context not found for UUID " + uuid); 287 return null; 288 } 289 290 /** 291 * Get an application context by the calling Apps name. 292 */ 293 App getByName(String name) { 294 Iterator<App> i = mApps.iterator(); 295 while (i.hasNext()) { 296 App entry = i.next(); 297 if (entry.name.equals(name)) return entry; 298 } 299 Log.e(TAG, "Context not found for name " + name); 300 return null; 301 } 302 303 /** 304 * Get Logging info by ID 305 */ 306 ScanStats getScanStatsById(int id) { 307 App temp = getById(id); 308 if (temp != null) { 309 return mScanStats.get(temp.name); 310 } 311 return null; 312 } 313 314 /** 315 * Get the device addresses for all connected devices 316 */ 317 Set<String> getConnectedDevices() { 318 Set<String> addresses = new HashSet<String>(); 319 Iterator<Connection> i = mConnections.iterator(); 320 while (i.hasNext()) { 321 Connection connection = i.next(); 322 addresses.add(connection.address); 323 } 324 return addresses; 325 } 326 327 /** 328 * Get an application context by a connection ID. 329 */ 330 App getByConnId(int connId) { 331 Iterator<Connection> ii = mConnections.iterator(); 332 while (ii.hasNext()) { 333 Connection connection = ii.next(); 334 if (connection.connId == connId){ 335 return getById(connection.appId); 336 } 337 } 338 return null; 339 } 340 341 /** 342 * Returns a connection ID for a given device address. 343 */ 344 Integer connIdByAddress(int id, String address) { 345 App entry = getById(id); 346 if (entry == null) return null; 347 348 Iterator<Connection> i = mConnections.iterator(); 349 while (i.hasNext()) { 350 Connection connection = i.next(); 351 if (connection.address.equals(address) && connection.appId == id) 352 return connection.connId; 353 } 354 return null; 355 } 356 357 /** 358 * Returns the device address for a given connection ID. 359 */ 360 String addressByConnId(int connId) { 361 Iterator<Connection> i = mConnections.iterator(); 362 while (i.hasNext()) { 363 Connection connection = i.next(); 364 if (connection.connId == connId) return connection.address; 365 } 366 return null; 367 } 368 369 List<Connection> getConnectionByApp(int appId) { 370 List<Connection> currentConnections = new ArrayList<Connection>(); 371 Iterator<Connection> i = mConnections.iterator(); 372 while (i.hasNext()) { 373 Connection connection = i.next(); 374 if (connection.appId == appId) 375 currentConnections.add(connection); 376 } 377 return currentConnections; 378 } 379 380 /** 381 * Erases all application context entries. 382 */ 383 void clear() { 384 synchronized (mApps) { 385 Iterator<App> i = mApps.iterator(); 386 while (i.hasNext()) { 387 App entry = i.next(); 388 entry.unlinkToDeath(); 389 i.remove(); 390 } 391 } 392 393 synchronized (mConnections) { 394 mConnections.clear(); 395 } 396 } 397 398 /** 399 * Returns connect device map with addr and appid 400 */ 401 Map<Integer, String> getConnectedMap(){ 402 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 403 for(Connection conn: mConnections){ 404 connectedmap.put(conn.appId, conn.address); 405 } 406 return connectedmap; 407 } 408 409 /** 410 * Logs debug information. 411 */ 412 void dump(StringBuilder sb) { 413 long currTime = System.currentTimeMillis(); 414 415 sb.append(" Entries: " + mScanStats.size() + "\n\n"); 416 417 Iterator<Map.Entry<String,ScanStats>> i = mScanStats.entrySet().iterator(); 418 while (i.hasNext()) { 419 Map.Entry<String, ScanStats> entry = i.next(); 420 421 String name = entry.getKey(); 422 ScanStats scanStats = entry.getValue(); 423 424 long maxScanTime = scanStats.maxScanTime; 425 long minScanTime = scanStats.minScanTime; 426 long currScanTime = 0; 427 428 if (scanStats.isScanning) { 429 currScanTime = currTime - scanStats.startTime; 430 minScanTime = Math.min(currScanTime, minScanTime); 431 maxScanTime = Math.max(currScanTime, maxScanTime); 432 } 433 434 if (minScanTime == Long.MAX_VALUE) { 435 minScanTime = 0; 436 } 437 438 long lastScan = 0; 439 if (scanStats.stopTime != 0) { 440 lastScan = currTime - scanStats.stopTime; 441 } 442 443 long avgScanTime = 0; 444 if (scanStats.scansStarted > 0) { 445 avgScanTime = (scanStats.totalScanTime + currScanTime) / 446 scanStats.scansStarted; 447 } 448 449 sb.append(" Application Name: " + name); 450 if (scanStats.isRegistered) sb.append(" (Registered)"); 451 sb.append("\n"); 452 453 sb.append(" LE scans (started/stopped) : " + 454 scanStats.scansStarted + " / " + 455 scanStats.scansStopped + "\n"); 456 sb.append(" Scan time in ms (min/max/avg) : " + 457 minScanTime + " / " + 458 maxScanTime + " / " + 459 avgScanTime + "\n"); 460 461 sb.append(" Time since last scan ended in ms : " + lastScan + "\n"); 462 463 sb.append(" Last " + scanStats.lastScans.size() + 464 " scans in ms (oldest first): "); 465 for (Long time : scanStats.lastScans) { 466 sb.append(time + " "); 467 } 468 sb.append("\n"); 469 470 if (scanStats.isRegistered) { 471 App appEntry = getByName(name); 472 sb.append(" Application ID : " + 473 appEntry.id + "\n"); 474 sb.append(" UUID : " + 475 appEntry.uuid + "\n"); 476 477 if (scanStats.isScanning) { 478 sb.append(" Current scan duration : " + 479 currScanTime + "\n"); 480 } 481 482 List<Connection> connections = getConnectionByApp(appEntry.id); 483 sb.append(" Connections: " + connections.size() + "\n"); 484 485 Iterator<Connection> ii = connections.iterator(); 486 while(ii.hasNext()) { 487 Connection connection = ii.next(); 488 sb.append(" " + connection.connId + ": " + 489 connection.address + "\n"); 490 } 491 } 492 sb.append("\n"); 493 } 494 } 495} 496