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