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 static 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 private final Object mConnectionsLock = new Object(); 211 212 /** 213 * Add an entry to the application context list. 214 */ add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)215 App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) { 216 int appUid = Binder.getCallingUid(); 217 String appName = service.getPackageManager().getNameForUid(appUid); 218 if (appName == null) { 219 // Assign an app name if one isn't found 220 appName = "Unknown App (UID: " + appUid + ")"; 221 } 222 synchronized (mAppsLock) { 223 AppScanStats appScanStats = mAppScanStats.get(appUid); 224 if (appScanStats == null) { 225 appScanStats = new AppScanStats(appName, workSource, this, service); 226 mAppScanStats.put(appUid, appScanStats); 227 } 228 App app = new App(uuid, callback, info, appName, appScanStats); 229 mApps.add(app); 230 appScanStats.isRegistered = true; 231 return app; 232 } 233 } 234 235 /** 236 * Add an entry to the application context list for advertiser. 237 */ add(int id, C callback, GattService service)238 App add(int id, C callback, GattService service) { 239 int appUid = Binder.getCallingUid(); 240 String appName = service.getPackageManager().getNameForUid(appUid); 241 if (appName == null) { 242 // Assign an app name if one isn't found 243 appName = "Unknown App (UID: " + appUid + ")"; 244 } 245 246 synchronized (mAppsLock) { 247 synchronized (this) { 248 if (!mAppAdvertiseStats.containsKey(id)) { 249 AppAdvertiseStats appAdvertiseStats = BluetoothMethodProxy.getInstance() 250 .createAppAdvertiseStats(appUid, id, appName, this, service); 251 mAppAdvertiseStats.put(id, appAdvertiseStats); 252 } 253 } 254 App app = getById(appUid); 255 if (app == null) { 256 app = new App(appUid, callback, appName); 257 mApps.add(app); 258 } 259 return app; 260 } 261 } 262 263 /** 264 * Remove the context for a given UUID 265 */ remove(UUID uuid)266 void remove(UUID uuid) { 267 synchronized (mAppsLock) { 268 Iterator<App> i = mApps.iterator(); 269 while (i.hasNext()) { 270 App entry = i.next(); 271 if (entry.uuid.equals(uuid)) { 272 entry.unlinkToDeath(); 273 entry.appScanStats.isRegistered = false; 274 i.remove(); 275 break; 276 } 277 } 278 } 279 } 280 281 /** 282 * Remove the context for a given application ID. 283 */ remove(int id)284 void remove(int id) { 285 boolean find = false; 286 synchronized (mAppsLock) { 287 Iterator<App> i = mApps.iterator(); 288 while (i.hasNext()) { 289 App entry = i.next(); 290 if (entry.id == id) { 291 find = true; 292 entry.unlinkToDeath(); 293 entry.appScanStats.isRegistered = false; 294 i.remove(); 295 break; 296 } 297 } 298 } 299 if (find) { 300 removeConnectionsByAppId(id); 301 } 302 } 303 getAllAppsIds()304 List<Integer> getAllAppsIds() { 305 List<Integer> appIds = new ArrayList(); 306 synchronized (mAppsLock) { 307 Iterator<App> i = mApps.iterator(); 308 while (i.hasNext()) { 309 App entry = i.next(); 310 appIds.add(entry.id); 311 } 312 } 313 return appIds; 314 } 315 316 /** 317 * Add a new connection for a given application ID. 318 */ addConnection(int id, int connId, String address)319 void addConnection(int id, int connId, String address) { 320 synchronized (mConnectionsLock) { 321 App entry = getById(id); 322 if (entry != null) { 323 mConnections.add(new Connection(connId, address, id)); 324 } 325 } 326 } 327 328 /** 329 * Remove a connection with the given ID. 330 */ removeConnection(int id, int connId)331 void removeConnection(int id, int connId) { 332 synchronized (mConnectionsLock) { 333 Iterator<Connection> i = mConnections.iterator(); 334 while (i.hasNext()) { 335 Connection connection = i.next(); 336 if (connection.connId == connId) { 337 i.remove(); 338 break; 339 } 340 } 341 } 342 } 343 344 /** 345 * Remove all connections for a given application ID. 346 */ removeConnectionsByAppId(int appId)347 void removeConnectionsByAppId(int appId) { 348 synchronized (mConnectionsLock) { 349 Iterator<Connection> i = mConnections.iterator(); 350 while (i.hasNext()) { 351 Connection connection = i.next(); 352 if (connection.appId == appId) { 353 i.remove(); 354 } 355 } 356 } 357 } 358 359 /** 360 * Get an application context by ID. 361 */ getById(int id)362 App getById(int id) { 363 synchronized (mAppsLock) { 364 Iterator<App> i = mApps.iterator(); 365 while (i.hasNext()) { 366 App entry = i.next(); 367 if (entry.id == id) { 368 return entry; 369 } 370 } 371 } 372 Log.e(TAG, "Context not found for ID " + id); 373 return null; 374 } 375 376 /** 377 * Get an application context by UUID. 378 */ getByUuid(UUID uuid)379 App getByUuid(UUID uuid) { 380 synchronized (mAppsLock) { 381 Iterator<App> i = mApps.iterator(); 382 while (i.hasNext()) { 383 App entry = i.next(); 384 if (entry.uuid.equals(uuid)) { 385 return entry; 386 } 387 } 388 } 389 Log.e(TAG, "Context not found for UUID " + uuid); 390 return null; 391 } 392 393 /** 394 * Get an application context by the calling Apps name. 395 */ getByName(String name)396 App getByName(String name) { 397 synchronized (mAppsLock) { 398 Iterator<App> i = mApps.iterator(); 399 while (i.hasNext()) { 400 App entry = i.next(); 401 if (entry.name.equals(name)) { 402 return entry; 403 } 404 } 405 } 406 Log.e(TAG, "Context not found for name " + name); 407 return null; 408 } 409 410 /** 411 * Get an application context by the context info object. 412 */ getByContextInfo(T contextInfo)413 App getByContextInfo(T contextInfo) { 414 synchronized (mAppsLock) { 415 Iterator<App> i = mApps.iterator(); 416 while (i.hasNext()) { 417 App entry = i.next(); 418 if (entry.info != null && entry.info.equals(contextInfo)) { 419 return entry; 420 } 421 } 422 } 423 Log.e(TAG, "Context not found for info " + contextInfo); 424 return null; 425 } 426 427 /** 428 * Get Logging info by ID 429 */ getAppScanStatsById(int id)430 AppScanStats getAppScanStatsById(int id) { 431 App temp = getById(id); 432 if (temp != null) { 433 return temp.appScanStats; 434 } 435 return null; 436 } 437 438 /** 439 * Get Logging info by application UID 440 */ getAppScanStatsByUid(int uid)441 AppScanStats getAppScanStatsByUid(int uid) { 442 return mAppScanStats.get(uid); 443 } 444 445 /** 446 * Remove the context for a given application ID. 447 */ removeAppAdvertiseStats(int id)448 void removeAppAdvertiseStats(int id) { 449 synchronized (this) { 450 mAppAdvertiseStats.remove(id); 451 } 452 } 453 454 /** 455 * Get Logging info by ID 456 */ getAppAdvertiseStatsById(int id)457 AppAdvertiseStats getAppAdvertiseStatsById(int id) { 458 synchronized (this) { 459 return mAppAdvertiseStats.get(id); 460 } 461 } 462 463 /** 464 * update the advertiser ID by the regiseter ID 465 */ setAdvertiserIdByRegId(int regId, int advertiserId)466 void setAdvertiserIdByRegId(int regId, int advertiserId) { 467 synchronized (this) { 468 AppAdvertiseStats stats = mAppAdvertiseStats.get(regId); 469 if (stats == null) { 470 return; 471 } 472 stats.setId(advertiserId); 473 mAppAdvertiseStats.remove(regId); 474 mAppAdvertiseStats.put(advertiserId, stats); 475 } 476 } 477 recordAdvertiseStart(int id, AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents)478 void recordAdvertiseStart(int id, AdvertisingSetParameters parameters, 479 AdvertiseData advertiseData, AdvertiseData scanResponse, 480 PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, 481 int duration, int maxExtAdvEvents) { 482 synchronized (this) { 483 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 484 if (stats == null) { 485 return; 486 } 487 stats.recordAdvertiseStart(parameters, advertiseData, scanResponse, 488 periodicParameters, periodicData, duration, maxExtAdvEvents); 489 int advertiseInstanceCount = mAppAdvertiseStats.size(); 490 Log.d(TAG, "advertiseInstanceCount is " + advertiseInstanceCount); 491 AppAdvertiseStats.recordAdvertiseInstanceCount(advertiseInstanceCount); 492 } 493 } 494 recordAdvertiseStop(int id)495 void recordAdvertiseStop(int id) { 496 synchronized (this) { 497 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 498 if (stats == null) { 499 return; 500 } 501 stats.recordAdvertiseStop(); 502 mAppAdvertiseStats.remove(id); 503 mLastAdvertises.add(stats); 504 } 505 } 506 enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents)507 void enableAdvertisingSet(int id, boolean enable, int duration, int maxExtAdvEvents) { 508 synchronized (this) { 509 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 510 if (stats == null) { 511 return; 512 } 513 stats.enableAdvertisingSet(enable, duration, maxExtAdvEvents); 514 } 515 } 516 setAdvertisingData(int id, AdvertiseData data)517 void setAdvertisingData(int id, AdvertiseData data) { 518 synchronized (this) { 519 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 520 if (stats == null) { 521 return; 522 } 523 stats.setAdvertisingData(data); 524 } 525 } 526 setScanResponseData(int id, AdvertiseData data)527 void setScanResponseData(int id, AdvertiseData data) { 528 synchronized (this) { 529 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 530 if (stats == null) { 531 return; 532 } 533 stats.setScanResponseData(data); 534 } 535 } 536 setAdvertisingParameters(int id, AdvertisingSetParameters parameters)537 void setAdvertisingParameters(int id, AdvertisingSetParameters parameters) { 538 synchronized (this) { 539 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 540 if (stats == null) { 541 return; 542 } 543 stats.setAdvertisingParameters(parameters); 544 } 545 } 546 setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters)547 void setPeriodicAdvertisingParameters(int id, PeriodicAdvertisingParameters parameters) { 548 synchronized (this) { 549 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 550 if (stats == null) { 551 return; 552 } 553 stats.setPeriodicAdvertisingParameters(parameters); 554 } 555 } 556 setPeriodicAdvertisingData(int id, AdvertiseData data)557 void setPeriodicAdvertisingData(int id, AdvertiseData data) { 558 synchronized (this) { 559 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 560 if (stats == null) { 561 return; 562 } 563 stats.setPeriodicAdvertisingData(data); 564 } 565 } 566 onPeriodicAdvertiseEnabled(int id, boolean enable)567 void onPeriodicAdvertiseEnabled(int id, boolean enable) { 568 synchronized (this) { 569 AppAdvertiseStats stats = mAppAdvertiseStats.get(id); 570 if (stats == null) { 571 return; 572 } 573 stats.onPeriodicAdvertiseEnabled(enable); 574 } 575 } 576 577 /** 578 * Get the device addresses for all connected devices 579 */ getConnectedDevices()580 Set<String> getConnectedDevices() { 581 Set<String> addresses = new HashSet<String>(); 582 synchronized (mConnectionsLock) { 583 Iterator<Connection> i = mConnections.iterator(); 584 while (i.hasNext()) { 585 Connection connection = i.next(); 586 addresses.add(connection.address); 587 } 588 } 589 return addresses; 590 } 591 592 /** 593 * Get an application context by a connection ID. 594 */ getByConnId(int connId)595 App getByConnId(int connId) { 596 int appId = -1; 597 synchronized (mConnectionsLock) { 598 Iterator<Connection> ii = mConnections.iterator(); 599 while (ii.hasNext()) { 600 Connection connection = ii.next(); 601 if (connection.connId == connId) { 602 appId = connection.appId; 603 break; 604 } 605 } 606 } 607 if (appId >= 0) { 608 return getById(appId); 609 } 610 return null; 611 } 612 613 /** 614 * Returns a connection ID for a given device address. 615 */ connIdByAddress(int id, String address)616 Integer connIdByAddress(int id, String address) { 617 App entry = getById(id); 618 if (entry == null) { 619 return null; 620 } 621 synchronized (mConnectionsLock) { 622 Iterator<Connection> i = mConnections.iterator(); 623 while (i.hasNext()) { 624 Connection connection = i.next(); 625 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) { 626 return connection.connId; 627 } 628 } 629 } 630 return null; 631 } 632 633 /** 634 * Returns the device address for a given connection ID. 635 */ addressByConnId(int connId)636 String addressByConnId(int connId) { 637 synchronized (mConnectionsLock) { 638 Iterator<Connection> i = mConnections.iterator(); 639 while (i.hasNext()) { 640 Connection connection = i.next(); 641 if (connection.connId == connId) { 642 return connection.address; 643 } 644 } 645 } 646 return null; 647 } 648 getConnectionByApp(int appId)649 List<Connection> getConnectionByApp(int appId) { 650 List<Connection> currentConnections = new ArrayList<Connection>(); 651 synchronized (mConnectionsLock) { 652 Iterator<Connection> i = mConnections.iterator(); 653 while (i.hasNext()) { 654 Connection connection = i.next(); 655 if (connection.appId == appId) { 656 currentConnections.add(connection); 657 } 658 } 659 } 660 return currentConnections; 661 } 662 663 /** 664 * Erases all application context entries. 665 */ clear()666 void clear() { 667 synchronized (mAppsLock) { 668 Iterator<App> i = mApps.iterator(); 669 while (i.hasNext()) { 670 App entry = i.next(); 671 entry.unlinkToDeath(); 672 if (entry.appScanStats != null) { 673 entry.appScanStats.isRegistered = false; 674 } 675 i.remove(); 676 } 677 } 678 679 synchronized (mConnectionsLock) { 680 mConnections.clear(); 681 } 682 683 synchronized (this) { 684 mAppAdvertiseStats.clear(); 685 mLastAdvertises.clear(); 686 } 687 } 688 689 /** 690 * Returns connect device map with addr and appid 691 */ getConnectedMap()692 Map<Integer, String> getConnectedMap() { 693 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 694 synchronized (mConnectionsLock) { 695 for (Connection conn : mConnections) { 696 connectedmap.put(conn.appId, conn.address); 697 } 698 } 699 return connectedmap; 700 } 701 702 /** 703 * Logs debug information. 704 */ dump(StringBuilder sb)705 void dump(StringBuilder sb) { 706 sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); 707 708 Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator(); 709 while (it.hasNext()) { 710 Map.Entry<Integer, AppScanStats> entry = it.next(); 711 712 AppScanStats appScanStats = entry.getValue(); 713 appScanStats.dumpToString(sb); 714 } 715 } 716 717 /** 718 * Logs advertiser debug information. 719 */ dumpAdvertiser(StringBuilder sb)720 void dumpAdvertiser(StringBuilder sb) { 721 synchronized (this) { 722 if (!mLastAdvertises.isEmpty()) { 723 sb.append("\n last " + mLastAdvertises.size() + " advertising:"); 724 for (AppAdvertiseStats stats : mLastAdvertises) { 725 AppAdvertiseStats.dumpToString(sb, stats); 726 } 727 sb.append("\n"); 728 } 729 730 if (!mAppAdvertiseStats.isEmpty()) { 731 sb.append(" Total number of ongoing advertising : " 732 + mAppAdvertiseStats.size()); 733 sb.append("\n Ongoing advertising:"); 734 for (Integer key : mAppAdvertiseStats.keySet()) { 735 AppAdvertiseStats stats = mAppAdvertiseStats.get(key); 736 AppAdvertiseStats.dumpToString(sb, stats); 737 } 738 } 739 sb.append("\n"); 740 } 741 Log.d(TAG, sb.toString()); 742 } 743 } 744