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