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.os.Binder; 19 import android.os.IBinder; 20 import android.os.IInterface; 21 import android.os.RemoteException; 22 import android.os.SystemClock; 23 import android.os.UserHandle; 24 import android.os.WorkSource; 25 import android.util.Log; 26 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.Iterator; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.NoSuchElementException; 34 import java.util.Set; 35 import java.util.UUID; 36 37 /** 38 * Helper class that keeps track of registered GATT applications. 39 * This class manages application callbacks and keeps track of GATT connections. 40 * @hide 41 */ 42 /*package*/ class ContextMap<C, T> { 43 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; 44 45 /** 46 * Connection class helps map connection IDs to device addresses. 47 */ 48 class Connection { 49 public int connId; 50 public String address; 51 public int appId; 52 public long startTime; 53 Connection(int connId, String address, int appId)54 Connection(int connId, String address, int appId) { 55 this.connId = connId; 56 this.address = address; 57 this.appId = appId; 58 this.startTime = SystemClock.elapsedRealtime(); 59 } 60 } 61 62 /** 63 * Application entry mapping UUIDs to appIDs and callbacks. 64 */ 65 class App { 66 /** The UUID of the application */ 67 public UUID uuid; 68 69 /** The id of the application */ 70 public int id; 71 72 /** The package name of the application */ 73 public String name; 74 75 /** Statistics for this app */ 76 public AppScanStats appScanStats; 77 78 /** Application callbacks */ 79 public C callback; 80 81 /** Context information */ 82 public T info; 83 /** Death receipient */ 84 private IBinder.DeathRecipient mDeathRecipient; 85 86 /** Flag to signal that transport is congested */ 87 public Boolean isCongested = false; 88 89 /** Whether the calling app has location permission */ 90 boolean hasLocationPermission; 91 92 /** Whether the calling app has bluetooth privileged permission */ 93 boolean hasBluetoothPrivilegedPermission; 94 95 /** The user handle of the app that started the scan */ 96 UserHandle mUserHandle; 97 98 /** Whether the calling app is targeting Q or better */ 99 boolean mIsQApp; 100 101 /** Whether the calling app has the network settings permission */ 102 boolean mHasNetworkSettingsPermission; 103 104 /** Whether the calling app has the network setup wizard permission */ 105 boolean mHasNetworkSetupWizardPermission; 106 107 /** Whether the calling app has the network setup wizard permission */ 108 boolean mHasScanWithoutLocationPermission; 109 110 /** Whether the calling app has disavowed the use of bluetooth for location */ 111 boolean mHasDisavowedLocation; 112 113 boolean mEligibleForSanitizedExposureNotification; 114 115 public List<String> mAssociatedDevices; 116 117 /** Internal callback info queue, waiting to be send on congestion clear */ 118 private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>(); 119 120 /** 121 * Creates a new app context. 122 */ App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)123 App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) { 124 this.uuid = uuid; 125 this.callback = callback; 126 this.info = info; 127 this.name = name; 128 this.appScanStats = appScanStats; 129 } 130 131 /** 132 * Link death recipient 133 */ linkToDeath(IBinder.DeathRecipient deathRecipient)134 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 135 // It might not be a binder object 136 if (callback == null) { 137 return; 138 } 139 try { 140 IBinder binder = ((IInterface) callback).asBinder(); 141 binder.linkToDeath(deathRecipient, 0); 142 mDeathRecipient = deathRecipient; 143 } catch (RemoteException e) { 144 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 145 } 146 } 147 148 /** 149 * Unlink death recipient 150 */ unlinkToDeath()151 void unlinkToDeath() { 152 if (mDeathRecipient != null) { 153 try { 154 IBinder binder = ((IInterface) callback).asBinder(); 155 binder.unlinkToDeath(mDeathRecipient, 0); 156 } catch (NoSuchElementException e) { 157 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 158 } 159 } 160 } 161 queueCallback(CallbackInfo callbackInfo)162 void queueCallback(CallbackInfo callbackInfo) { 163 mCongestionQueue.add(callbackInfo); 164 } 165 popQueuedCallback()166 CallbackInfo popQueuedCallback() { 167 if (mCongestionQueue.size() == 0) { 168 return null; 169 } 170 return mCongestionQueue.remove(0); 171 } 172 } 173 174 /** Our internal application list */ 175 private List<App> mApps = new ArrayList<App>(); 176 177 /** Internal map to keep track of logging information by app name */ 178 private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>(); 179 180 /** Internal list of connected devices **/ 181 private Set<Connection> mConnections = new HashSet<Connection>(); 182 183 /** 184 * Add an entry to the application context list. 185 */ add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)186 App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) { 187 int appUid = Binder.getCallingUid(); 188 String appName = service.getPackageManager().getNameForUid(appUid); 189 if (appName == null) { 190 // Assign an app name if one isn't found 191 appName = "Unknown App (UID: " + appUid + ")"; 192 } 193 synchronized (mApps) { 194 AppScanStats appScanStats = mAppScanStats.get(appUid); 195 if (appScanStats == null) { 196 appScanStats = new AppScanStats(appName, workSource, this, service); 197 mAppScanStats.put(appUid, appScanStats); 198 } 199 App app = new App(uuid, callback, info, appName, appScanStats); 200 mApps.add(app); 201 appScanStats.isRegistered = true; 202 return app; 203 } 204 } 205 206 /** 207 * Remove the context for a given UUID 208 */ remove(UUID uuid)209 void remove(UUID uuid) { 210 synchronized (mApps) { 211 Iterator<App> i = mApps.iterator(); 212 while (i.hasNext()) { 213 App entry = i.next(); 214 if (entry.uuid.equals(uuid)) { 215 entry.unlinkToDeath(); 216 entry.appScanStats.isRegistered = false; 217 i.remove(); 218 break; 219 } 220 } 221 } 222 } 223 224 /** 225 * Remove the context for a given application ID. 226 */ remove(int id)227 void remove(int id) { 228 boolean find = false; 229 synchronized (mApps) { 230 Iterator<App> i = mApps.iterator(); 231 while (i.hasNext()) { 232 App entry = i.next(); 233 if (entry.id == id) { 234 find = true; 235 entry.unlinkToDeath(); 236 entry.appScanStats.isRegistered = false; 237 i.remove(); 238 break; 239 } 240 } 241 } 242 if (find) { 243 removeConnectionsByAppId(id); 244 } 245 } 246 getAllAppsIds()247 List<Integer> getAllAppsIds() { 248 List<Integer> appIds = new ArrayList(); 249 synchronized (mApps) { 250 Iterator<App> i = mApps.iterator(); 251 while (i.hasNext()) { 252 App entry = i.next(); 253 appIds.add(entry.id); 254 } 255 } 256 return appIds; 257 } 258 259 /** 260 * Add a new connection for a given application ID. 261 */ addConnection(int id, int connId, String address)262 void addConnection(int id, int connId, String address) { 263 synchronized (mConnections) { 264 App entry = getById(id); 265 if (entry != null) { 266 mConnections.add(new Connection(connId, address, id)); 267 } 268 } 269 } 270 271 /** 272 * Remove a connection with the given ID. 273 */ removeConnection(int id, int connId)274 void removeConnection(int id, int connId) { 275 synchronized (mConnections) { 276 Iterator<Connection> i = mConnections.iterator(); 277 while (i.hasNext()) { 278 Connection connection = i.next(); 279 if (connection.connId == connId) { 280 i.remove(); 281 break; 282 } 283 } 284 } 285 } 286 287 /** 288 * Remove all connections for a given application ID. 289 */ removeConnectionsByAppId(int appId)290 void removeConnectionsByAppId(int appId) { 291 synchronized (mConnections) { 292 Iterator<Connection> i = mConnections.iterator(); 293 while (i.hasNext()) { 294 Connection connection = i.next(); 295 if (connection.appId == appId) { 296 i.remove(); 297 } 298 } 299 } 300 } 301 302 /** 303 * Get an application context by ID. 304 */ getById(int id)305 App getById(int id) { 306 synchronized (mApps) { 307 Iterator<App> i = mApps.iterator(); 308 while (i.hasNext()) { 309 App entry = i.next(); 310 if (entry.id == id) { 311 return entry; 312 } 313 } 314 } 315 Log.e(TAG, "Context not found for ID " + id); 316 return null; 317 } 318 319 /** 320 * Get an application context by UUID. 321 */ getByUuid(UUID uuid)322 App getByUuid(UUID uuid) { 323 synchronized (mApps) { 324 Iterator<App> i = mApps.iterator(); 325 while (i.hasNext()) { 326 App entry = i.next(); 327 if (entry.uuid.equals(uuid)) { 328 return entry; 329 } 330 } 331 } 332 Log.e(TAG, "Context not found for UUID " + uuid); 333 return null; 334 } 335 336 /** 337 * Get an application context by the calling Apps name. 338 */ getByName(String name)339 App getByName(String name) { 340 synchronized (mApps) { 341 Iterator<App> i = mApps.iterator(); 342 while (i.hasNext()) { 343 App entry = i.next(); 344 if (entry.name.equals(name)) { 345 return entry; 346 } 347 } 348 } 349 Log.e(TAG, "Context not found for name " + name); 350 return null; 351 } 352 353 /** 354 * Get an application context by the context info object. 355 */ getByContextInfo(T contextInfo)356 App getByContextInfo(T contextInfo) { 357 synchronized (mApps) { 358 Iterator<App> i = mApps.iterator(); 359 while (i.hasNext()) { 360 App entry = i.next(); 361 if (entry.info != null && entry.info.equals(contextInfo)) { 362 return entry; 363 } 364 } 365 } 366 Log.e(TAG, "Context not found for info " + contextInfo); 367 return null; 368 } 369 370 /** 371 * Get Logging info by ID 372 */ getAppScanStatsById(int id)373 AppScanStats getAppScanStatsById(int id) { 374 App temp = getById(id); 375 if (temp != null) { 376 return temp.appScanStats; 377 } 378 return null; 379 } 380 381 /** 382 * Get Logging info by application UID 383 */ getAppScanStatsByUid(int uid)384 AppScanStats getAppScanStatsByUid(int uid) { 385 return mAppScanStats.get(uid); 386 } 387 388 /** 389 * Get the device addresses for all connected devices 390 */ getConnectedDevices()391 Set<String> getConnectedDevices() { 392 Set<String> addresses = new HashSet<String>(); 393 synchronized (mConnections) { 394 Iterator<Connection> i = mConnections.iterator(); 395 while (i.hasNext()) { 396 Connection connection = i.next(); 397 addresses.add(connection.address); 398 } 399 } 400 return addresses; 401 } 402 403 /** 404 * Get an application context by a connection ID. 405 */ getByConnId(int connId)406 App getByConnId(int connId) { 407 int appId = -1; 408 synchronized (mConnections) { 409 Iterator<Connection> ii = mConnections.iterator(); 410 while (ii.hasNext()) { 411 Connection connection = ii.next(); 412 if (connection.connId == connId) { 413 appId = connection.appId; 414 break; 415 } 416 } 417 } 418 if (appId >= 0) { 419 return getById(appId); 420 } 421 return null; 422 } 423 424 /** 425 * Returns a connection ID for a given device address. 426 */ connIdByAddress(int id, String address)427 Integer connIdByAddress(int id, String address) { 428 App entry = getById(id); 429 if (entry == null) { 430 return null; 431 } 432 synchronized (mConnections) { 433 Iterator<Connection> i = mConnections.iterator(); 434 while (i.hasNext()) { 435 Connection connection = i.next(); 436 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) { 437 return connection.connId; 438 } 439 } 440 } 441 return null; 442 } 443 444 /** 445 * Returns the device address for a given connection ID. 446 */ addressByConnId(int connId)447 String addressByConnId(int connId) { 448 synchronized (mConnections) { 449 Iterator<Connection> i = mConnections.iterator(); 450 while (i.hasNext()) { 451 Connection connection = i.next(); 452 if (connection.connId == connId) { 453 return connection.address; 454 } 455 } 456 } 457 return null; 458 } 459 getConnectionByApp(int appId)460 List<Connection> getConnectionByApp(int appId) { 461 List<Connection> currentConnections = new ArrayList<Connection>(); 462 synchronized (mConnections) { 463 Iterator<Connection> i = mConnections.iterator(); 464 while (i.hasNext()) { 465 Connection connection = i.next(); 466 if (connection.appId == appId) { 467 currentConnections.add(connection); 468 } 469 } 470 } 471 return currentConnections; 472 } 473 474 /** 475 * Erases all application context entries. 476 */ clear()477 void clear() { 478 synchronized (mApps) { 479 Iterator<App> i = mApps.iterator(); 480 while (i.hasNext()) { 481 App entry = i.next(); 482 entry.unlinkToDeath(); 483 entry.appScanStats.isRegistered = false; 484 i.remove(); 485 } 486 } 487 488 synchronized (mConnections) { 489 mConnections.clear(); 490 } 491 } 492 493 /** 494 * Returns connect device map with addr and appid 495 */ getConnectedMap()496 Map<Integer, String> getConnectedMap() { 497 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 498 synchronized (mConnections) { 499 for (Connection conn : mConnections) { 500 connectedmap.put(conn.appId, conn.address); 501 } 502 } 503 return connectedmap; 504 } 505 506 /** 507 * Logs debug information. 508 */ dump(StringBuilder sb)509 void dump(StringBuilder sb) { 510 sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); 511 512 Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator(); 513 while (it.hasNext()) { 514 Map.Entry<Integer, AppScanStats> entry = it.next(); 515 516 AppScanStats appScanStats = entry.getValue(); 517 appScanStats.dumpToString(sb); 518 } 519 } 520 } 521