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