ContextMap.java revision 45c63ddeca7ccb3bbf1cb3a7234c89c9eee177cf
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.bluetooth.le.ScanSettings; 19import android.content.Context; 20import android.os.Binder; 21import android.os.IBinder; 22import android.os.IBinder.DeathRecipient; 23import android.os.IInterface; 24import android.os.RemoteException; 25import android.util.Log; 26import java.text.DateFormat; 27import java.text.SimpleDateFormat; 28import java.util.ArrayList; 29import java.util.Date; 30import java.util.HashSet; 31import java.util.Iterator; 32import java.util.LinkedList; 33import java.util.List; 34import java.util.NoSuchElementException; 35import java.util.Set; 36import java.util.UUID; 37import java.util.HashMap; 38import java.util.Map; 39 40import com.android.bluetooth.btservice.BluetoothProto; 41 42/** 43 * Helper class that keeps track of registered GATT applications. 44 * This class manages application callbacks and keeps track of GATT connections. 45 * @hide 46 */ 47/*package*/ class ContextMap<T> { 48 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; 49 50 static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 51 static final int NUM_SCAN_EVENTS_KEPT = 20; 52 ArrayList<BluetoothProto.ScanEvent> mScanEvents = 53 new ArrayList<BluetoothProto.ScanEvent>(NUM_SCAN_EVENTS_KEPT); 54 55 /** 56 * ScanStats class helps keep track of information about scans 57 * on a per application basis. 58 */ 59 class ScanStats { 60 static final int NUM_SCAN_DURATIONS_KEPT = 5; 61 62 String appName; 63 int scansStarted = 0; 64 int scansStopped = 0; 65 boolean isScanning = false; 66 boolean isRegistered = false; 67 boolean isOpportunisticScan = false; 68 boolean isBackgroundScan = false; 69 long minScanTime = Long.MAX_VALUE; 70 long maxScanTime = 0; 71 long totalScanTime = 0; 72 List<Long> lastScans = new ArrayList<Long>(NUM_SCAN_DURATIONS_KEPT + 1); 73 List<Long> lastScanTimestamps = new ArrayList<Long>(NUM_SCAN_DURATIONS_KEPT + 1); 74 long startTime = 0; 75 long stopTime = 0; 76 77 public ScanStats(String name) { 78 appName = name; 79 } 80 81 void recordScanStart(ScanSettings settings) { 82 this.scansStarted++; 83 isScanning = true; 84 startTime = System.currentTimeMillis(); 85 if (settings != null) { 86 isOpportunisticScan = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC; 87 isBackgroundScan = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0; 88 } 89 90 BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent(); 91 scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START); 92 scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE); 93 scanEvent.setInitiator(appName); 94 scanEvent.setEventTimeMillis(System.currentTimeMillis()); 95 96 lastScanTimestamps.add(startTime); 97 if (lastScanTimestamps.size() > NUM_SCAN_DURATIONS_KEPT) { 98 lastScanTimestamps.remove(0); 99 } 100 101 synchronized(mScanEvents) { 102 if(mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) 103 mScanEvents.remove(0); 104 mScanEvents.add(scanEvent); 105 } 106 } 107 108 void recordScanStop() { 109 this.scansStopped++; 110 isScanning = false; 111 stopTime = System.currentTimeMillis(); 112 long currTime = stopTime - startTime; 113 114 isOpportunisticScan = false; 115 isBackgroundScan = false; 116 117 minScanTime = Math.min(currTime, minScanTime); 118 maxScanTime = Math.max(currTime, maxScanTime); 119 totalScanTime += currTime; 120 lastScans.add(currTime); 121 if (lastScans.size() > NUM_SCAN_DURATIONS_KEPT) { 122 lastScans.remove(0); 123 } 124 125 BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent(); 126 scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP); 127 scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE); 128 scanEvent.setInitiator(appName); 129 scanEvent.setEventTimeMillis(System.currentTimeMillis()); 130 synchronized(mScanEvents) { 131 if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) 132 mScanEvents.remove(0); 133 mScanEvents.add(scanEvent); 134 } 135 } 136 } 137 138 /** 139 * Connection class helps map connection IDs to device addresses. 140 */ 141 class Connection { 142 int connId; 143 String address; 144 int appId; 145 146 Connection(int connId, String address,int appId) { 147 this.connId = connId; 148 this.address = address; 149 this.appId = appId; 150 } 151 } 152 153 /** 154 * Application entry mapping UUIDs to appIDs and callbacks. 155 */ 156 class App { 157 /** The UUID of the application */ 158 UUID uuid; 159 160 /** The id of the application */ 161 int id; 162 163 /** The package name of the application */ 164 String name; 165 166 /** Application callbacks */ 167 T callback; 168 169 /** Death receipient */ 170 private IBinder.DeathRecipient mDeathRecipient; 171 172 /** Flag to signal that transport is congested */ 173 Boolean isCongested = false; 174 175 /** Internal callback info queue, waiting to be send on congestion clear */ 176 private List<CallbackInfo> congestionQueue = new ArrayList<CallbackInfo>(); 177 178 /** 179 * Creates a new app context. 180 */ 181 App(UUID uuid, T callback, String name) { 182 this.uuid = uuid; 183 this.callback = callback; 184 this.name = name; 185 } 186 187 /** 188 * Link death recipient 189 */ 190 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 191 try { 192 IBinder binder = ((IInterface)callback).asBinder(); 193 binder.linkToDeath(deathRecipient, 0); 194 mDeathRecipient = deathRecipient; 195 } catch (RemoteException e) { 196 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 197 } 198 } 199 200 /** 201 * Unlink death recipient 202 */ 203 void unlinkToDeath() { 204 if (mDeathRecipient != null) { 205 try { 206 IBinder binder = ((IInterface)callback).asBinder(); 207 binder.unlinkToDeath(mDeathRecipient,0); 208 } catch (NoSuchElementException e) { 209 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 210 } 211 } 212 } 213 214 void queueCallback(CallbackInfo callbackInfo) { 215 congestionQueue.add(callbackInfo); 216 } 217 218 CallbackInfo popQueuedCallback() { 219 if (congestionQueue.size() == 0) return null; 220 return congestionQueue.remove(0); 221 } 222 } 223 224 /** Our internal application list */ 225 List<App> mApps = new ArrayList<App>(); 226 227 /** Internal map to keep track of logging information by app name */ 228 HashMap<String, ScanStats> mScanStats = new HashMap<String, ScanStats>(); 229 230 /** Internal list of connected devices **/ 231 Set<Connection> mConnections = new HashSet<Connection>(); 232 233 /** 234 * Add an entry to the application context list. 235 */ 236 void add(UUID uuid, T callback, Context context) { 237 String appName = context.getPackageManager().getNameForUid( 238 Binder.getCallingUid()); 239 if (appName == null) { 240 // Assign an app name if one isn't found 241 appName = "Unknown App (UID: " + Binder.getCallingUid() + ")"; 242 } 243 synchronized (mApps) { 244 mApps.add(new App(uuid, callback, appName)); 245 ScanStats scanStats = mScanStats.get(appName); 246 if (scanStats == null) { 247 scanStats = new ScanStats(appName); 248 mScanStats.put(appName, scanStats); 249 } 250 scanStats.isRegistered = true; 251 } 252 } 253 254 /** 255 * Remove the context for a given UUID 256 */ 257 void remove(UUID uuid) { 258 synchronized (mApps) { 259 Iterator<App> i = mApps.iterator(); 260 while (i.hasNext()) { 261 App entry = i.next(); 262 if (entry.uuid.equals(uuid)) { 263 entry.unlinkToDeath(); 264 mScanStats.get(entry.name).isRegistered = false; 265 i.remove(); 266 break; 267 } 268 } 269 } 270 } 271 272 /** 273 * Remove the context for a given application ID. 274 */ 275 void remove(int id) { 276 synchronized (mApps) { 277 Iterator<App> i = mApps.iterator(); 278 while (i.hasNext()) { 279 App entry = i.next(); 280 if (entry.id == id) { 281 entry.unlinkToDeath(); 282 mScanStats.get(entry.name).isRegistered = false; 283 i.remove(); 284 break; 285 } 286 } 287 } 288 } 289 290 /** 291 * Add a new connection for a given application ID. 292 */ 293 void addConnection(int id, int connId, String address) { 294 synchronized (mConnections) { 295 App entry = getById(id); 296 if (entry != null){ 297 mConnections.add(new Connection(connId, address, id)); 298 } 299 } 300 } 301 302 /** 303 * Remove a connection with the given ID. 304 */ 305 void removeConnection(int id, int connId) { 306 synchronized (mConnections) { 307 Iterator<Connection> i = mConnections.iterator(); 308 while (i.hasNext()) { 309 Connection connection = i.next(); 310 if (connection.connId == connId) { 311 i.remove(); 312 break; 313 } 314 } 315 } 316 } 317 318 /** 319 * Get an application context by ID. 320 */ 321 App getById(int id) { 322 Iterator<App> i = mApps.iterator(); 323 while (i.hasNext()) { 324 App entry = i.next(); 325 if (entry.id == id) return entry; 326 } 327 Log.e(TAG, "Context not found for ID " + id); 328 return null; 329 } 330 331 /** 332 * Get an application context by UUID. 333 */ 334 App getByUuid(UUID uuid) { 335 Iterator<App> i = mApps.iterator(); 336 while (i.hasNext()) { 337 App entry = i.next(); 338 if (entry.uuid.equals(uuid)) return entry; 339 } 340 Log.e(TAG, "Context not found for UUID " + uuid); 341 return null; 342 } 343 344 /** 345 * Get an application context by the calling Apps name. 346 */ 347 App getByName(String name) { 348 Iterator<App> i = mApps.iterator(); 349 while (i.hasNext()) { 350 App entry = i.next(); 351 if (entry.name.equals(name)) return entry; 352 } 353 Log.e(TAG, "Context not found for name " + name); 354 return null; 355 } 356 357 /** 358 * Get Logging info by ID 359 */ 360 ScanStats getScanStatsById(int id) { 361 App temp = getById(id); 362 if (temp != null) { 363 return mScanStats.get(temp.name); 364 } 365 return null; 366 } 367 368 /** 369 * Get Logging info by application name 370 */ 371 ScanStats getScanStatsByName(String name) { 372 return mScanStats.get(name); 373 } 374 375 /** 376 * Get the device addresses for all connected devices 377 */ 378 Set<String> getConnectedDevices() { 379 Set<String> addresses = new HashSet<String>(); 380 Iterator<Connection> i = mConnections.iterator(); 381 while (i.hasNext()) { 382 Connection connection = i.next(); 383 addresses.add(connection.address); 384 } 385 return addresses; 386 } 387 388 /** 389 * Get an application context by a connection ID. 390 */ 391 App getByConnId(int connId) { 392 Iterator<Connection> ii = mConnections.iterator(); 393 while (ii.hasNext()) { 394 Connection connection = ii.next(); 395 if (connection.connId == connId){ 396 return getById(connection.appId); 397 } 398 } 399 return null; 400 } 401 402 /** 403 * Returns a connection ID for a given device address. 404 */ 405 Integer connIdByAddress(int id, String address) { 406 App entry = getById(id); 407 if (entry == null) return null; 408 409 Iterator<Connection> i = mConnections.iterator(); 410 while (i.hasNext()) { 411 Connection connection = i.next(); 412 if (connection.address.equals(address) && connection.appId == id) 413 return connection.connId; 414 } 415 return null; 416 } 417 418 /** 419 * Returns the device address for a given connection ID. 420 */ 421 String addressByConnId(int connId) { 422 Iterator<Connection> i = mConnections.iterator(); 423 while (i.hasNext()) { 424 Connection connection = i.next(); 425 if (connection.connId == connId) return connection.address; 426 } 427 return null; 428 } 429 430 List<Connection> getConnectionByApp(int appId) { 431 List<Connection> currentConnections = new ArrayList<Connection>(); 432 Iterator<Connection> i = mConnections.iterator(); 433 while (i.hasNext()) { 434 Connection connection = i.next(); 435 if (connection.appId == appId) 436 currentConnections.add(connection); 437 } 438 return currentConnections; 439 } 440 441 /** 442 * Erases all application context entries. 443 */ 444 void clear() { 445 synchronized (mApps) { 446 Iterator<App> i = mApps.iterator(); 447 while (i.hasNext()) { 448 App entry = i.next(); 449 entry.unlinkToDeath(); 450 i.remove(); 451 } 452 } 453 454 synchronized (mConnections) { 455 mConnections.clear(); 456 } 457 } 458 459 /** 460 * Returns connect device map with addr and appid 461 */ 462 Map<Integer, String> getConnectedMap(){ 463 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 464 for(Connection conn: mConnections){ 465 connectedmap.put(conn.appId, conn.address); 466 } 467 return connectedmap; 468 } 469 470 /** 471 * Logs debug information. 472 */ 473 void dump(StringBuilder sb) { 474 long currTime = System.currentTimeMillis(); 475 476 sb.append(" Entries: " + mScanStats.size() + "\n\n"); 477 478 Iterator<Map.Entry<String,ScanStats>> it = mScanStats.entrySet().iterator(); 479 while (it.hasNext()) { 480 Map.Entry<String, ScanStats> entry = it.next(); 481 482 String name = entry.getKey(); 483 ScanStats scanStats = entry.getValue(); 484 485 long maxScanTime = scanStats.maxScanTime; 486 long minScanTime = scanStats.minScanTime; 487 long currScanTime = 0; 488 489 if (scanStats.isScanning) { 490 currScanTime = currTime - scanStats.startTime; 491 minScanTime = Math.min(currScanTime, minScanTime); 492 maxScanTime = Math.max(currScanTime, maxScanTime); 493 } 494 495 if (minScanTime == Long.MAX_VALUE) { 496 minScanTime = 0; 497 } 498 499 long lastScan = 0; 500 if (scanStats.stopTime != 0) { 501 lastScan = currTime - scanStats.stopTime; 502 } 503 504 long avgScanTime = 0; 505 if (scanStats.scansStarted > 0) { 506 avgScanTime = (scanStats.totalScanTime + currScanTime) / 507 scanStats.scansStarted; 508 } 509 510 sb.append(" " + name); 511 if (scanStats.isRegistered) sb.append(" (Registered)"); 512 if (scanStats.isOpportunisticScan) sb.append(" (Opportunistic)"); 513 if (scanStats.isBackgroundScan) sb.append(" (Background)"); 514 sb.append("\n"); 515 516 sb.append(" LE scans (started/stopped) : " + 517 scanStats.scansStarted + " / " + 518 scanStats.scansStopped + "\n"); 519 sb.append(" Scan time in ms (min/max/avg) : " + 520 minScanTime + " / " + 521 maxScanTime + " / " + 522 avgScanTime + "\n"); 523 524 if (scanStats.lastScans.size() != 0) { 525 sb.append(" Last " + scanStats.lastScans.size() + 526 " scans (timestamp - duration):\n"); 527 528 for (int i = 0; i < scanStats.lastScans.size(); i++) { 529 Date timestamp = new Date(scanStats.lastScanTimestamps.get(i)); 530 sb.append(" " + dateFormat.format(timestamp) + " - "); 531 sb.append(scanStats.lastScans.get(i) + "ms\n"); 532 } 533 } 534 535 if (scanStats.isRegistered) { 536 App appEntry = getByName(name); 537 sb.append(" Application ID : " + 538 appEntry.id + "\n"); 539 sb.append(" UUID : " + 540 appEntry.uuid + "\n"); 541 542 if (scanStats.isScanning) { 543 sb.append(" Current scan duration in ms : " + 544 currScanTime + "\n"); 545 } 546 547 List<Connection> connections = getConnectionByApp(appEntry.id); 548 sb.append(" Connections: " + connections.size() + "\n"); 549 550 Iterator<Connection> ii = connections.iterator(); 551 while(ii.hasNext()) { 552 Connection connection = ii.next(); 553 sb.append(" " + connection.connId + ": " + 554 connection.address + "\n"); 555 } 556 } 557 sb.append("\n"); 558 } 559 } 560 561 void dumpProto(BluetoothProto.BluetoothLog proto) { 562 synchronized(mScanEvents) { 563 for (BluetoothProto.ScanEvent event : mScanEvents) { 564 proto.addScanEvent(event); 565 } 566 } 567 } 568} 569