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.bluetooth.le.AdvertiseData; 19 import android.bluetooth.le.AdvertisingSetParameters; 20 import android.bluetooth.le.PeriodicAdvertisingParameters; 21 import android.os.Binder; 22 import android.os.IBinder; 23 import android.os.IInterface; 24 import android.os.RemoteException; 25 import android.os.SystemClock; 26 import android.os.UserHandle; 27 import android.os.WorkSource; 28 import android.util.Log; 29 30 import androidx.annotation.VisibleForTesting; 31 32 import com.android.bluetooth.BluetoothMethodProxy; 33 import com.android.internal.annotations.GuardedBy; 34 35 import com.google.common.collect.EvictingQueue; 36 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.Iterator; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.NoSuchElementException; 44 import java.util.Set; 45 import java.util.UUID; 46 47 /** 48 * Helper class that keeps track of registered GATT applications. 49 * This class manages application callbacks and keeps track of GATT connections. 50 * @hide 51 */ 52 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 53 public class ContextMap<C, T> { 54 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; 55 56 /** 57 * Connection class helps map connection IDs to device addresses. 58 */ 59 class Connection { 60 public int connId; 61 public String address; 62 public int appId; 63 public long startTime; 64 Connection(int connId, String address, int appId)65 Connection(int connId, String address, int appId) { 66 this.connId = connId; 67 this.address = address; 68 this.appId = appId; 69 this.startTime = SystemClock.elapsedRealtime(); 70 } 71 } 72 73 /** 74 * Application entry mapping UUIDs to appIDs and callbacks. 75 */ 76 class App { 77 /** The UUID of the application */ 78 public UUID uuid; 79 80 /** The id of the application */ 81 public int id; 82 83 /** The package name of the application */ 84 public String name; 85 86 /** Statistics for this app */ 87 public AppScanStats appScanStats; 88 89 /** Application callbacks */ 90 public C callback; 91 92 /** Context information */ 93 public T info; 94 /** Death receipient */ 95 private IBinder.DeathRecipient mDeathRecipient; 96 97 /** Flag to signal that transport is congested */ 98 public Boolean isCongested = false; 99 100 /** Whether the calling app has location permission */ 101 boolean hasLocationPermission; 102 103 /** Whether the calling app has bluetooth privileged permission */ 104 boolean hasBluetoothPrivilegedPermission; 105 106 /** The user handle of the app that started the scan */ 107 UserHandle mUserHandle; 108 109 /** Whether the calling app has the network settings permission */ 110 boolean mHasNetworkSettingsPermission; 111 112 /** Whether the calling app has the network setup wizard permission */ 113 boolean mHasNetworkSetupWizardPermission; 114 115 /** Whether the calling app has the network setup wizard permission */ 116 boolean mHasScanWithoutLocationPermission; 117 118 /** Whether the calling app has disavowed the use of bluetooth for location */ 119 boolean mHasDisavowedLocation; 120 121 boolean mEligibleForSanitizedExposureNotification; 122 123 public List<String> mAssociatedDevices; 124 125 /** Internal callback info queue, waiting to be send on congestion clear */ 126 private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>(); 127 128 /** 129 * Creates a new app context. 130 */ App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)131 App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) { 132 this.uuid = uuid; 133 this.callback = callback; 134 this.info = info; 135 this.name = name; 136 this.appScanStats = appScanStats; 137 } 138 139 /** 140 * Creates a new app context for advertiser. 141 */ App(int id, C callback, String name)142 App(int id, C callback, String name) { 143 this.id = id; 144 this.callback = callback; 145 this.name = name; 146 } 147 148 /** 149 * Link death recipient 150 */ linkToDeath(IBinder.DeathRecipient deathRecipient)151 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 152 // It might not be a binder object 153 if (callback == null) { 154 return; 155 } 156 try { 157 IBinder binder = ((IInterface) callback).asBinder(); 158 binder.linkToDeath(deathRecipient, 0); 159 mDeathRecipient = deathRecipient; 160 } catch (RemoteException e) { 161 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 162 } 163 } 164 165 /** 166 * Unlink death recipient 167 */ unlinkToDeath()168 void unlinkToDeath() { 169 if (mDeathRecipient != null) { 170 try { 171 IBinder binder = ((IInterface) callback).asBinder(); 172 binder.unlinkToDeath(mDeathRecipient, 0); 173 } catch (NoSuchElementException e) { 174 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 175 } 176 } 177 } 178 queueCallback(CallbackInfo callbackInfo)179 void queueCallback(CallbackInfo callbackInfo) { 180 mCongestionQueue.add(callbackInfo); 181 } 182 popQueuedCallback()183 CallbackInfo popQueuedCallback() { 184 if (mCongestionQueue.size() == 0) { 185 return null; 186 } 187 return mCongestionQueue.remove(0); 188 } 189 } 190 191 /** Our internal application list */ 192 private final Object mAppsLock = new Object(); 193 @GuardedBy("mAppsLock") 194 private List<App> mApps = new ArrayList<App>(); 195 196 /** Internal map to keep track of logging information by app name */ 197 private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>(); 198 199 /** Internal map to keep track of logging information by advertise id */ 200 private final Map<Integer, AppAdvertiseStats> mAppAdvertiseStats = 201 new HashMap<Integer, AppAdvertiseStats>(); 202 203 private static final int ADVERTISE_STATE_MAX_SIZE = 5; 204 205 private final EvictingQueue<AppAdvertiseStats> mLastAdvertises = 206 EvictingQueue.create(ADVERTISE_STATE_MAX_SIZE); 207 208 /** Internal list of connected devices **/ 209 private Set<Connection> mConnections = new HashSet<Connection>(); 210 211 /** 212 * Add an entry to the application context list. 213 */ add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)214 App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) { 215 int appUid = Binder.getCallingUid(); 216 String appName = service.getPackageManager().getNameForUid(appUid); 217 if (appName == null) { 218 // Assign an app name if one isn't found 219 appName = "Unknown App (UID: " + appUid + ")"; 220 } 221 synchronized (mApps) { 222 AppScanStats appScanStats = mAppScanStats.get(appUid); 223 if (appScanStats == null) { 224 appScanStats = new AppScanStats(appName, workSource, this, service); 225 mAppScanStats.put(appUid, appScanStats); 226 } 227 App app = new App(uuid, callback, info, appName, appScanStats); 228 mApps.add(app); 229 appScanStats.isRegistered = true; 230 return app; 231 } 232 } 233 234 /** 235 * Add an entry to the application context list for advertiser. 236 */ add(int id, C callback, GattService service)237 App add(int id, C callback, GattService service) { 238 int appUid = Binder.getCallingUid(); 239 String appName = service.getPackageManager().getNameForUid(appUid); 240 if (appName == null) { 241 // Assign an app name if one isn't found 242 appName = "Unknown App (UID: " + appUid + ")"; 243 } 244 245 synchronized (mAppsLock) { 246 synchronized (this) { 247 if (!mAppAdvertiseStats.containsKey(id)) { 248 AppAdvertiseStats appAdvertiseStats = BluetoothMethodProxy.getInstance() 249 .createAppAdvertiseStats(appUid, id, appName, this, service); 250 mAppAdvertiseStats.put(id, appAdvertiseStats); 251 } 252 } 253 App app = getById(appUid); 254 if (app == null) { 255 app = new App(appUid, callback, appName); 256 mApps.add(app); 257 } 258 return app; 259 } 260 } 261 262 /** 263 * Remove the context for a given UUID 264 */ remove(UUID uuid)265 void remove(UUID uuid) { 266 synchronized (mApps) { 267 Iterator<App> i = mApps.iterator(); 268 while (i.hasNext()) { 269 App entry = i.next(); 270 if (entry.uuid.equals(uuid)) { 271 entry.unlinkToDeath(); 272 entry.appScanStats.isRegistered = false; 273 i.remove(); 274 break; 275 } 276 } 277 } 278 } 279 280 /** 281 * Remove the context for a given application ID. 282 */ remove(int id)283 void remove(int id) { 284 boolean find = false; 285 synchronized (mApps) { 286 Iterator<App> i = mApps.iterator(); 287 while (i.hasNext()) { 288 App entry = i.next(); 289 if (entry.id == id) { 290 find = true; 291 entry.unlinkToDeath(); 292 entry.appScanStats.isRegistered = false; 293 i.remove(); 294 break; 295 } 296 } 297 } 298 if (find) { 299 removeConnectionsByAppId(id); 300 } 301 } 302 getAllAppsIds()303 List<Integer> getAllAppsIds() { 304 List<Integer> appIds = new ArrayList(); 305 synchronized (mApps) { 306 Iterator<App> i = mApps.iterator(); 307 while (i.hasNext()) { 308 App entry = i.next(); 309 appIds.add(entry.id); 310 } 311 } 312 return appIds; 313 } 314 315 /** 316 * Add a new connection for a given application ID. 317 */ addConnection(int id, int connId, String address)318 void addConnection(int id, int connId, String address) { 319 synchronized (mConnections) { 320 App entry = getById(id); 321 if (entry != null) { 322 mConnections.add(new Connection(connId, address, id)); 323 } 324 } 325 } 326 327 /** 328 * Remove a connection with the given ID. 329 */ removeConnection(int id, int connId)330 void removeConnection(int id, int connId) { 331 synchronized (mConnections) { 332 Iterator<Connection> i = mConnections.iterator(); 333 while (i.hasNext()) { 334 Connection connection = i.next(); 335 if (connection.connId == connId) { 336 i.remove(); 337 break; 338 } 339 } 340 } 341 } 342 343 /** 344 * Remove all connections for a given application ID. 345 */ removeConnectionsByAppId(int appId)346 void removeConnectionsByAppId(int appId) { 347 synchronized (mConnections) { 348 Iterator<Connection> i = mConnections.iterator(); 349 while (i.hasNext()) { 350 Connection connection = i.next(); 351 if (connection.appId == appId) { 352 i.remove(); 353 } 354 } 355 } 356 } 357 358 /** 359 * Get an application context by ID. 360 */ getById(int id)361 App getById(int id) { 362 synchronized (mApps) { 363 Iterator<App> i = mApps.iterator(); 364 while (i.hasNext()) { 365 App entry = i.next(); 366 if (entry.id == id) { 367 return entry; 368 } 369 } 370 } 371 Log.e(TAG, "Context not found for ID " + id); 372 return null; 373 } 374 375 /** 376 * Get an application context by UUID. 377 */ getByUuid(UUID uuid)378 App getByUuid(UUID uuid) { 379 synchronized (mApps) { 380 Iterator<App> i = mApps.iterator(); 381 while (i.hasNext()) { 382 App entry = i.next(); 383 if (entry.uuid.equals(uuid)) { 384 return entry; 385 } 386 } 387 } 388 Log.e(TAG, "Context not found for UUID " + uuid); 389 return null; 390 } 391 392 /** 393 * Get an application context by the calling Apps name. 394 */ getByName(String name)395 App getByName(String name) { 396 synchronized (mApps) { 397 Iterator<App> i = mApps.iterator(); 398 while (i.hasNext()) { 399 App entry = i.next(); 400 if (entry.name.equals(name)) { 401 return entry; 402 } 403 } 404 } 405 Log.e(TAG, "Context not found for name " + name); 406 return null; 407 } 408 409 /** 410 * Get an application context by the context info object. 411 */ getByContextInfo(T contextInfo)412 App getByContextInfo(T contextInfo) { 413 synchronized (mApps) { 414 Iterator<App> i = mApps.iterator(); 415 while (i.hasNext()) { 416 App entry = i.next(); 417 if (entry.info != null && entry.info.equals(contextInfo)) { 418 return entry; 419 } 420 } 421 } 422 Log.e(TAG, "Context not found for info " + contextInfo); 423 return null; 424 } 425 426 /** 427 * Get Logging info by ID 428 */ getAppScanStatsById(int id)429 AppScanStats getAppScanStatsById(int id) { 430 App temp = getById(id); 431 if (temp != null) { 432 return temp.appScanStats; 433 } 434 return null; 435 } 436 437 /** 438 * Get Logging info by application UID 439 */ getAppScanStatsByUid(int uid)440 AppScanStats getAppScanStatsByUid(int uid) { 441 return mAppScanStats.get(uid); 442 } 443 444 /** 445 * Remove the context for a given application ID. 446 */ removeAppAdvertiseStats(int id)447 void removeAppAdvertiseStats(int id) { 448 synchronized (this) { 449 mAppAdvertiseStats.remove(id); 450 } 451 } 452 453 /** 454 * Get Logging info by ID 455 */ getAppAdvertiseStatsById(int id)456 AppAdvertiseStats getAppAdvertiseStatsById(int id) { 457 synchronized (this) { 458 return mAppAdvertiseStats.get(id); 459 } 460 } 461 462 /** 463 * update the advertiser ID by the regiseter ID 464 */ setAdvertiserIdByRegId(int regId, int advertiserId)465 void setAdvertiserIdByRegId(int regId, int advertiserId) { 466 synchronized (this) { 467 AppAdvertiseStats stats = mAppAdvertiseStats.get(regId); 468 if (stats == null) { 469 return; 470 } 471 stats.setId(advertiserId); 472 mAppAdvertiseStats.remove(regId); 473 mAppAdvertiseStats.put(advertiserId, stats); 474 } 475 } 476 recordAdvertiseStart(int id, AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents)477 void recordAdvertiseStart(int id, AdvertisingSetParameters parameters, 478 AdvertiseData advertiseData, AdvertiseData scanResponse, 479 PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, 480 int duration, int maxExtAdvEvents) { 481 synchronized (this) { 482 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 483 if (stats == null) { 484 return; 485 } 486 stats.recordAdvertiseStart(parameters, advertiseData, scanResponse, 487 periodicParameters, periodicData, duration, maxExtAdvEvents); 488 } 489 } 490 recordAdvertiseStop(int id)491 void recordAdvertiseStop(int id) { 492 synchronized (this) { 493 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 494 if (stats == null) { 495 return; 496 } 497 stats.recordAdvertiseStop(); 498 mAppAdvertiseStats.remove(id); 499 mLastAdvertises.add(stats); 500 } 501 } 502 enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents)503 void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) { 504 synchronized (this) { 505 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 506 if (stats == null) { 507 return; 508 } 509 stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents); 510 } 511 } 512 setAdvertisingData(int id, AdvertiseData data)513 void setAdvertisingData(int id, AdvertiseData data) { 514 synchronized (this) { 515 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 516 if (stats == null) { 517 return; 518 } 519 stats.setAdvertisingData(data); 520 } 521 } 522 setScanResponseData(int id, AdvertiseData data)523 void setScanResponseData(int id, AdvertiseData data) { 524 synchronized (this) { 525 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 526 if (stats == null) { 527 return; 528 } 529 stats.setScanResponseData(data); 530 } 531 } 532 setAdvertisingParameters(int id, AdvertisingSetParameters parameters)533 void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) { 534 synchronized (this) { 535 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 536 if (stats == null) { 537 return; 538 } 539 stats.setAdvertisingParameters(parameters); 540 } 541 } 542 setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters)543 void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) { 544 synchronized (this) { 545 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 546 if (stats == null) { 547 return; 548 } 549 stats.setPeriodicAdvertisingParameters(parameters); 550 } 551 } 552 setPeriodicAdvertisingData(int id, AdvertiseData data)553 void setPeriodicAdvertisingData(int id, AdvertiseData data) { 554 synchronized (this) { 555 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 556 if (stats == null) { 557 return; 558 } 559 stats.setPeriodicAdvertisingData(data); 560 } 561 } 562 onPeriodicAdvertiseEnabled(int id, boolean enable)563 void onPeriodicAdvertiseEnabled(int id, boolean enable) { 564 synchronized (this) { 565 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 566 if (stats == null) { 567 return; 568 } 569 stats.onPeriodicAdvertiseEnabled(enable); 570 } 571 } 572 573 /** 574 * Get the device addresses for all connected devices 575 */ getConnectedDevices()576 Set<String> getConnectedDevices() { 577 Set<String> addresses = new HashSet<String>(); 578 synchronized (mConnections) { 579 Iterator<Connection> i = mConnections.iterator(); 580 while (i.hasNext()) { 581 Connection connection = i.next(); 582 addresses.add(connection.address); 583 } 584 } 585 return addresses; 586 } 587 588 /** 589 * Get an application context by a connection ID. 590 */ getByConnId(int connId)591 App getByConnId(int connId) { 592 int appId = -1; 593 synchronized (mConnections) { 594 Iterator<Connection> ii = mConnections.iterator(); 595 while (ii.hasNext()) { 596 Connection connection = ii.next(); 597 if (connection.connId == connId) { 598 appId = connection.appId; 599 break; 600 } 601 } 602 } 603 if (appId >= 0) { 604 return getById(appId); 605 } 606 return null; 607 } 608 609 /** 610 * Returns a connection ID for a given device address. 611 */ connIdByAddress(int id, String address)612 Integer connIdByAddress(int id, String address) { 613 App entry = getById(id); 614 if (entry == null) { 615 return null; 616 } 617 synchronized (mConnections) { 618 Iterator<Connection> i = mConnections.iterator(); 619 while (i.hasNext()) { 620 Connection connection = i.next(); 621 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) { 622 return connection.connId; 623 } 624 } 625 } 626 return null; 627 } 628 629 /** 630 * Returns the device address for a given connection ID. 631 */ addressByConnId(int connId)632 String addressByConnId(int connId) { 633 synchronized (mConnections) { 634 Iterator<Connection> i = mConnections.iterator(); 635 while (i.hasNext()) { 636 Connection connection = i.next(); 637 if (connection.connId == connId) { 638 return connection.address; 639 } 640 } 641 } 642 return null; 643 } 644 getConnectionByApp(int appId)645 List<Connection> getConnectionByApp(int appId) { 646 List<Connection> currentConnections = new ArrayList<Connection>(); 647 synchronized (mConnections) { 648 Iterator<Connection> i = mConnections.iterator(); 649 while (i.hasNext()) { 650 Connection connection = i.next(); 651 if (connection.appId == appId) { 652 currentConnections.add(connection); 653 } 654 } 655 } 656 return currentConnections; 657 } 658 659 /** 660 * Erases all application context entries. 661 */ clear()662 void clear() { 663 synchronized (mApps) { 664 Iterator<App> i = mApps.iterator(); 665 while (i.hasNext()) { 666 App entry = i.next(); 667 entry.unlinkToDeath(); 668 if (entry.appScanStats != null) { 669 entry.appScanStats.isRegistered = false; 670 } 671 i.remove(); 672 } 673 } 674 675 synchronized (mConnections) { 676 mConnections.clear(); 677 } 678 679 synchronized (this) { 680 mAppAdvertiseStats.clear(); 681 mLastAdvertises.clear(); 682 } 683 } 684 685 /** 686 * Returns connect device map with addr and appid 687 */ getConnectedMap()688 Map<Integer, String> getConnectedMap() { 689 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 690 synchronized (mConnections) { 691 for (Connection conn : mConnections) { 692 connectedmap.put(conn.appId, conn.address); 693 } 694 } 695 return connectedmap; 696 } 697 698 /** 699 * Logs debug information. 700 */ dump(StringBuilder sb)701 void dump(StringBuilder sb) { 702 sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); 703 704 Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator(); 705 while (it.hasNext()) { 706 Map.Entry<Integer, AppScanStats> entry = it.next(); 707 708 AppScanStats appScanStats = entry.getValue(); 709 appScanStats.dumpToString(sb); 710 } 711 } 712 713 /** 714 * Logs advertiser debug information. 715 */ dumpAdvertiser(StringBuilder sb)716 void dumpAdvertiser(StringBuilder sb) { 717 synchronized (this) { 718 if (!mLastAdvertises.isEmpty()) { 719 sb.append("\n last " + mLastAdvertises.size() + " advertising:"); 720 for (AppAdvertiseStats stats : mLastAdvertises) { 721 AppAdvertiseStats.dumpToString(sb, stats); 722 } 723 sb.append("\n"); 724 } 725 726 if (!mAppAdvertiseStats.isEmpty()) { 727 sb.append(" Total number of ongoing advertising : " 728 + mAppAdvertiseStats.size()); 729 sb.append("\n Ongoing advertising:"); 730 for (Integer key : mAppAdvertiseStats.keySet()) { 731 AppAdvertiseStats stats = mAppAdvertiseStats.get(key); 732 AppAdvertiseStats.dumpToString(sb, stats); 733 } 734 } 735 sb.append("\n"); 736 } 737 Log.d(TAG, sb.toString()); 738 } 739 } 740