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