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         /** 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          */
    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          */
    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          */
    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 
    138         void queueCallback(CallbackInfo callbackInfo) {
    139             congestionQueue.add(callbackInfo);
    140         }
    141 
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    346     AppScanStats getAppScanStatsByUid(int uid) {
    347         return mAppScanStats.get(uid);
    348     }
    349 
    350     /**
    351      * Get the device addresses for all connected devices
    352      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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