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