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.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