/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.gatt; import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.IInterface; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; import java.util.HashMap; import java.util.Map; import com.android.bluetooth.btservice.BluetoothProto; /** * Helper class that keeps track of registered GATT applications. * This class manages application callbacks and keeps track of GATT connections. * @hide */ /*package*/ class ContextMap { private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; /** * Connection class helps map connection IDs to device addresses. */ class Connection { int connId; String address; int appId; long startTime; Connection(int connId, String address,int appId) { this.connId = connId; this.address = address; this.appId = appId; this.startTime = System.currentTimeMillis(); } } /** * Application entry mapping UUIDs to appIDs and callbacks. */ class App { /** The UUID of the application */ UUID uuid; /** The id of the application */ int id; /** The package name of the application */ String name; /** Statistics for this app */ AppScanStats appScanStats; /** Application callbacks */ T callback; /** Death receipient */ private IBinder.DeathRecipient mDeathRecipient; /** Flag to signal that transport is congested */ Boolean isCongested = false; /** Internal callback info queue, waiting to be send on congestion clear */ private List congestionQueue = new ArrayList(); /** * Creates a new app context. */ App(UUID uuid, T callback, String name, AppScanStats appScanStats) { this.uuid = uuid; this.callback = callback; this.name = name; this.appScanStats = appScanStats; } /** * Link death recipient */ void linkToDeath(IBinder.DeathRecipient deathRecipient) { try { IBinder binder = ((IInterface)callback).asBinder(); binder.linkToDeath(deathRecipient, 0); mDeathRecipient = deathRecipient; } catch (RemoteException e) { Log.e(TAG, "Unable to link deathRecipient for app id " + id); } } /** * Unlink death recipient */ void unlinkToDeath() { if (mDeathRecipient != null) { try { IBinder binder = ((IInterface)callback).asBinder(); binder.unlinkToDeath(mDeathRecipient,0); } catch (NoSuchElementException e) { Log.e(TAG, "Unable to unlink deathRecipient for app id " + id); } } } void queueCallback(CallbackInfo callbackInfo) { congestionQueue.add(callbackInfo); } CallbackInfo popQueuedCallback() { if (congestionQueue.size() == 0) return null; return congestionQueue.remove(0); } } /** Our internal application list */ List mApps = new ArrayList(); /** Internal map to keep track of logging information by app name */ HashMap mAppScanStats = new HashMap(); /** Internal list of connected devices **/ Set mConnections = new HashSet(); /** * Add an entry to the application context list. */ void add(UUID uuid, T callback, GattService service) { String appName = service.getPackageManager().getNameForUid( Binder.getCallingUid()); if (appName == null) { // Assign an app name if one isn't found appName = "Unknown App (UID: " + Binder.getCallingUid() + ")"; } synchronized (mApps) { AppScanStats appScanStats = mAppScanStats.get(appName); if (appScanStats == null) { appScanStats = new AppScanStats(appName, this, service); mAppScanStats.put(appName, appScanStats); } mApps.add(new App(uuid, callback, appName, appScanStats)); appScanStats.isRegistered = true; } } /** * Remove the context for a given UUID */ void remove(UUID uuid) { synchronized (mApps) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); if (entry.uuid.equals(uuid)) { entry.unlinkToDeath(); entry.appScanStats.isRegistered = false; i.remove(); break; } } } } /** * Remove the context for a given application ID. */ void remove(int id) { synchronized (mApps) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); if (entry.id == id) { removeConnectionsByAppId(id); entry.unlinkToDeath(); entry.appScanStats.isRegistered = false; i.remove(); break; } } } } /** * Add a new connection for a given application ID. */ void addConnection(int id, int connId, String address) { synchronized (mConnections) { App entry = getById(id); if (entry != null) { mConnections.add(new Connection(connId, address, id)); } } } /** * Remove a connection with the given ID. */ void removeConnection(int id, int connId) { synchronized (mConnections) { Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); if (connection.connId == connId) { i.remove(); break; } } } } /** * Remove all connections for a given application ID. */ void removeConnectionsByAppId(int appId) { Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); if (connection.appId == appId) { i.remove(); } } } /** * Get an application context by ID. */ App getById(int id) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); if (entry.id == id) return entry; } Log.e(TAG, "Context not found for ID " + id); return null; } /** * Get an application context by UUID. */ App getByUuid(UUID uuid) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); if (entry.uuid.equals(uuid)) return entry; } Log.e(TAG, "Context not found for UUID " + uuid); return null; } /** * Get an application context by the calling Apps name. */ App getByName(String name) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); if (entry.name.equals(name)) return entry; } Log.e(TAG, "Context not found for name " + name); return null; } /** * Get Logging info by ID */ AppScanStats getAppScanStatsById(int id) { App temp = getById(id); if (temp != null) { return temp.appScanStats; } return null; } /** * Get Logging info by application name */ AppScanStats getAppScanStatsByName(String name) { return mAppScanStats.get(name); } /** * Get the device addresses for all connected devices */ Set getConnectedDevices() { Set addresses = new HashSet(); Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); addresses.add(connection.address); } return addresses; } /** * Get an application context by a connection ID. */ App getByConnId(int connId) { Iterator ii = mConnections.iterator(); while (ii.hasNext()) { Connection connection = ii.next(); if (connection.connId == connId){ return getById(connection.appId); } } return null; } /** * Returns a connection ID for a given device address. */ Integer connIdByAddress(int id, String address) { App entry = getById(id); if (entry == null) return null; Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); if (connection.address.equals(address) && connection.appId == id) return connection.connId; } return null; } /** * Returns the device address for a given connection ID. */ String addressByConnId(int connId) { Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); if (connection.connId == connId) return connection.address; } return null; } List getConnectionByApp(int appId) { List currentConnections = new ArrayList(); Iterator i = mConnections.iterator(); while (i.hasNext()) { Connection connection = i.next(); if (connection.appId == appId) currentConnections.add(connection); } return currentConnections; } /** * Erases all application context entries. */ void clear() { synchronized (mApps) { Iterator i = mApps.iterator(); while (i.hasNext()) { App entry = i.next(); entry.unlinkToDeath(); entry.appScanStats.isRegistered = false; i.remove(); } } synchronized (mConnections) { mConnections.clear(); } } /** * Returns connect device map with addr and appid */ Map getConnectedMap(){ Map connectedmap = new HashMap(); for(Connection conn: mConnections){ connectedmap.put(conn.appId, conn.address); } return connectedmap; } /** * Logs debug information. */ void dump(StringBuilder sb) { sb.append(" Entries: " + mAppScanStats.size() + "\n\n"); Iterator> it = mAppScanStats.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); String name = entry.getKey(); AppScanStats appScanStats = entry.getValue(); appScanStats.dumpToString(sb); } } }