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 boolean mEligibleForSanitizedExposureNotification; 111 112 public List<String> mAssociatedDevices; 113 114 /** Internal callback info queue, waiting to be send on congestion clear */ 115 private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>(); 116 117 /** 118 * Creates a new app context. 119 */ App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats)120 App(UUID uuid, C callback, T info, String name, AppScanStats appScanStats) { 121 this.uuid = uuid; 122 this.callback = callback; 123 this.info = info; 124 this.name = name; 125 this.appScanStats = appScanStats; 126 } 127 128 /** 129 * Link death recipient 130 */ linkToDeath(IBinder.DeathRecipient deathRecipient)131 void linkToDeath(IBinder.DeathRecipient deathRecipient) { 132 // It might not be a binder object 133 if (callback == null) { 134 return; 135 } 136 try { 137 IBinder binder = ((IInterface) callback).asBinder(); 138 binder.linkToDeath(deathRecipient, 0); 139 mDeathRecipient = deathRecipient; 140 } catch (RemoteException e) { 141 Log.e(TAG, "Unable to link deathRecipient for app id " + id); 142 } 143 } 144 145 /** 146 * Unlink death recipient 147 */ unlinkToDeath()148 void unlinkToDeath() { 149 if (mDeathRecipient != null) { 150 try { 151 IBinder binder = ((IInterface) callback).asBinder(); 152 binder.unlinkToDeath(mDeathRecipient, 0); 153 } catch (NoSuchElementException e) { 154 Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); 155 } 156 } 157 } 158 queueCallback(CallbackInfo callbackInfo)159 void queueCallback(CallbackInfo callbackInfo) { 160 mCongestionQueue.add(callbackInfo); 161 } 162 popQueuedCallback()163 CallbackInfo popQueuedCallback() { 164 if (mCongestionQueue.size() == 0) { 165 return null; 166 } 167 return mCongestionQueue.remove(0); 168 } 169 } 170 171 /** Our internal application list */ 172 private List<App> mApps = new ArrayList<App>(); 173 174 /** Internal map to keep track of logging information by app name */ 175 HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>(); 176 177 /** Internal list of connected devices **/ 178 Set<Connection> mConnections = new HashSet<Connection>(); 179 180 /** 181 * Add an entry to the application context list. 182 */ add(UUID uuid, WorkSource workSource, C callback, T info, GattService service)183 App add(UUID uuid, WorkSource workSource, C callback, T info, GattService service) { 184 int appUid = Binder.getCallingUid(); 185 String appName = service.getPackageManager().getNameForUid(appUid); 186 if (appName == null) { 187 // Assign an app name if one isn't found 188 appName = "Unknown App (UID: " + appUid + ")"; 189 } 190 synchronized (mApps) { 191 AppScanStats appScanStats = mAppScanStats.get(appUid); 192 if (appScanStats == null) { 193 appScanStats = new AppScanStats(appName, workSource, this, service); 194 mAppScanStats.put(appUid, appScanStats); 195 } 196 App app = new App(uuid, callback, info, appName, appScanStats); 197 mApps.add(app); 198 appScanStats.isRegistered = true; 199 return app; 200 } 201 } 202 203 /** 204 * Remove the context for a given UUID 205 */ remove(UUID uuid)206 void remove(UUID uuid) { 207 synchronized (mApps) { 208 Iterator<App> i = mApps.iterator(); 209 while (i.hasNext()) { 210 App entry = i.next(); 211 if (entry.uuid.equals(uuid)) { 212 entry.unlinkToDeath(); 213 entry.appScanStats.isRegistered = false; 214 i.remove(); 215 break; 216 } 217 } 218 } 219 } 220 221 /** 222 * Remove the context for a given application ID. 223 */ remove(int id)224 void remove(int id) { 225 synchronized (mApps) { 226 Iterator<App> i = mApps.iterator(); 227 while (i.hasNext()) { 228 App entry = i.next(); 229 if (entry.id == id) { 230 removeConnectionsByAppId(id); 231 entry.unlinkToDeath(); 232 entry.appScanStats.isRegistered = false; 233 i.remove(); 234 break; 235 } 236 } 237 } 238 } 239 getAllAppsIds()240 List<Integer> getAllAppsIds() { 241 List<Integer> appIds = new ArrayList(); 242 synchronized (mApps) { 243 Iterator<App> i = mApps.iterator(); 244 while (i.hasNext()) { 245 App entry = i.next(); 246 appIds.add(entry.id); 247 } 248 } 249 return appIds; 250 } 251 252 /** 253 * Add a new connection for a given application ID. 254 */ addConnection(int id, int connId, String address)255 void addConnection(int id, int connId, String address) { 256 synchronized (mConnections) { 257 App entry = getById(id); 258 if (entry != null) { 259 mConnections.add(new Connection(connId, address, id)); 260 } 261 } 262 } 263 264 /** 265 * Remove a connection with the given ID. 266 */ removeConnection(int id, int connId)267 void removeConnection(int id, int connId) { 268 synchronized (mConnections) { 269 Iterator<Connection> i = mConnections.iterator(); 270 while (i.hasNext()) { 271 Connection connection = i.next(); 272 if (connection.connId == connId) { 273 i.remove(); 274 break; 275 } 276 } 277 } 278 } 279 280 /** 281 * Remove all connections for a given application ID. 282 */ removeConnectionsByAppId(int appId)283 void removeConnectionsByAppId(int appId) { 284 Iterator<Connection> i = mConnections.iterator(); 285 while (i.hasNext()) { 286 Connection connection = i.next(); 287 if (connection.appId == appId) { 288 i.remove(); 289 } 290 } 291 } 292 293 /** 294 * Get an application context by ID. 295 */ getById(int id)296 App getById(int id) { 297 synchronized (mApps) { 298 Iterator<App> i = mApps.iterator(); 299 while (i.hasNext()) { 300 App entry = i.next(); 301 if (entry.id == id) { 302 return entry; 303 } 304 } 305 } 306 Log.e(TAG, "Context not found for ID " + id); 307 return null; 308 } 309 310 /** 311 * Get an application context by UUID. 312 */ getByUuid(UUID uuid)313 App getByUuid(UUID uuid) { 314 synchronized (mApps) { 315 Iterator<App> i = mApps.iterator(); 316 while (i.hasNext()) { 317 App entry = i.next(); 318 if (entry.uuid.equals(uuid)) { 319 return entry; 320 } 321 } 322 } 323 Log.e(TAG, "Context not found for UUID " + uuid); 324 return null; 325 } 326 327 /** 328 * Get an application context by the calling Apps name. 329 */ getByName(String name)330 App getByName(String name) { 331 synchronized (mApps) { 332 Iterator<App> i = mApps.iterator(); 333 while (i.hasNext()) { 334 App entry = i.next(); 335 if (entry.name.equals(name)) { 336 return entry; 337 } 338 } 339 } 340 Log.e(TAG, "Context not found for name " + name); 341 return null; 342 } 343 344 /** 345 * Get an application context by the context info object. 346 */ getByContextInfo(T contextInfo)347 App getByContextInfo(T contextInfo) { 348 synchronized (mApps) { 349 Iterator<App> i = mApps.iterator(); 350 while (i.hasNext()) { 351 App entry = i.next(); 352 if (entry.info != null && entry.info.equals(contextInfo)) { 353 return entry; 354 } 355 } 356 } 357 Log.e(TAG, "Context not found for info " + contextInfo); 358 return null; 359 } 360 361 /** 362 * Get Logging info by ID 363 */ getAppScanStatsById(int id)364 AppScanStats getAppScanStatsById(int id) { 365 App temp = getById(id); 366 if (temp != null) { 367 return temp.appScanStats; 368 } 369 return null; 370 } 371 372 /** 373 * Get Logging info by application UID 374 */ getAppScanStatsByUid(int uid)375 AppScanStats getAppScanStatsByUid(int uid) { 376 return mAppScanStats.get(uid); 377 } 378 379 /** 380 * Get the device addresses for all connected devices 381 */ getConnectedDevices()382 Set<String> getConnectedDevices() { 383 Set<String> addresses = new HashSet<String>(); 384 Iterator<Connection> i = mConnections.iterator(); 385 while (i.hasNext()) { 386 Connection connection = i.next(); 387 addresses.add(connection.address); 388 } 389 return addresses; 390 } 391 392 /** 393 * Get an application context by a connection ID. 394 */ getByConnId(int connId)395 App getByConnId(int connId) { 396 Iterator<Connection> ii = mConnections.iterator(); 397 while (ii.hasNext()) { 398 Connection connection = ii.next(); 399 if (connection.connId == connId) { 400 return getById(connection.appId); 401 } 402 } 403 return null; 404 } 405 406 /** 407 * Returns a connection ID for a given device address. 408 */ connIdByAddress(int id, String address)409 Integer connIdByAddress(int id, String address) { 410 App entry = getById(id); 411 if (entry == null) { 412 return null; 413 } 414 415 Iterator<Connection> i = mConnections.iterator(); 416 while (i.hasNext()) { 417 Connection connection = i.next(); 418 if (connection.address.equalsIgnoreCase(address) && connection.appId == id) { 419 return connection.connId; 420 } 421 } 422 return null; 423 } 424 425 /** 426 * Returns the device address for a given connection ID. 427 */ addressByConnId(int connId)428 String addressByConnId(int connId) { 429 Iterator<Connection> i = mConnections.iterator(); 430 while (i.hasNext()) { 431 Connection connection = i.next(); 432 if (connection.connId == connId) { 433 return connection.address; 434 } 435 } 436 return null; 437 } 438 getConnectionByApp(int appId)439 List<Connection> getConnectionByApp(int appId) { 440 List<Connection> currentConnections = new ArrayList<Connection>(); 441 Iterator<Connection> i = mConnections.iterator(); 442 while (i.hasNext()) { 443 Connection connection = i.next(); 444 if (connection.appId == appId) { 445 currentConnections.add(connection); 446 } 447 } 448 return currentConnections; 449 } 450 451 /** 452 * Erases all application context entries. 453 */ clear()454 void clear() { 455 synchronized (mApps) { 456 Iterator<App> i = mApps.iterator(); 457 while (i.hasNext()) { 458 App entry = i.next(); 459 entry.unlinkToDeath(); 460 entry.appScanStats.isRegistered = false; 461 i.remove(); 462 } 463 } 464 465 synchronized (mConnections) { 466 mConnections.clear(); 467 } 468 } 469 470 /** 471 * Returns connect device map with addr and appid 472 */ getConnectedMap()473 Map<Integer, String> getConnectedMap() { 474 Map<Integer, String> connectedmap = new HashMap<Integer, String>(); 475 for (Connection conn : mConnections) { 476 connectedmap.put(conn.appId, conn.address); 477 } 478 return connectedmap; 479 } 480 481 /** 482 * Logs debug information. 483 */ dump(StringBuilder sb)484 void dump(StringBuilder sb) { 485 sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); 486 487 Iterator<Map.Entry<Integer, AppScanStats>> it = mAppScanStats.entrySet().iterator(); 488 while (it.hasNext()) { 489 Map.Entry<Integer, AppScanStats> entry = it.next(); 490 491 AppScanStats appScanStats = entry.getValue(); 492 appScanStats.dumpToString(sb); 493 } 494 } 495 } 496