Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2010 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 
     17 package com.android.server.wifi;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.AppOpsManager;
     21 import android.bluetooth.BluetoothAdapter;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.PackageManager;
     27 import android.database.ContentObserver;
     28 import android.net.wifi.IWifiManager;
     29 import android.net.wifi.ScanResult;
     30 import android.net.wifi.WifiInfo;
     31 import android.net.wifi.WifiManager;
     32 import android.net.wifi.WifiStateMachine;
     33 import android.net.wifi.WifiConfiguration;
     34 import android.net.wifi.WifiWatchdogStateMachine;
     35 import android.net.DhcpInfo;
     36 import android.net.DhcpResults;
     37 import android.net.LinkAddress;
     38 import android.net.NetworkUtils;
     39 import android.net.RouteInfo;
     40 import android.os.Binder;
     41 import android.os.Handler;
     42 import android.os.Messenger;
     43 import android.os.HandlerThread;
     44 import android.os.IBinder;
     45 import android.os.INetworkManagementService;
     46 import android.os.Message;
     47 import android.os.RemoteException;
     48 import android.os.SystemProperties;
     49 import android.os.UserHandle;
     50 import android.os.WorkSource;
     51 import android.provider.Settings;
     52 import android.util.Log;
     53 import android.util.Slog;
     54 
     55 import java.io.FileDescriptor;
     56 import java.io.PrintWriter;
     57 import java.net.InetAddress;
     58 import java.net.Inet4Address;
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 import java.util.concurrent.atomic.AtomicBoolean;
     62 
     63 import com.android.internal.R;
     64 import com.android.internal.app.IBatteryStats;
     65 import com.android.internal.telephony.TelephonyIntents;
     66 import com.android.internal.util.AsyncChannel;
     67 import com.android.server.am.BatteryStatsService;
     68 import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
     69 import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
     70 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
     71 import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
     72 import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
     73 import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
     74 import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
     75 import static com.android.server.wifi.WifiController.CMD_SET_AP;
     76 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
     77 /**
     78  * WifiService handles remote WiFi operation requests by implementing
     79  * the IWifiManager interface.
     80  *
     81  * @hide
     82  */
     83 public final class WifiService extends IWifiManager.Stub {
     84     private static final String TAG = "WifiService";
     85     private static final boolean DBG = false;
     86 
     87     final WifiStateMachine mWifiStateMachine;
     88 
     89     private final Context mContext;
     90 
     91     final LockList mLocks = new LockList();
     92     // some wifi lock statistics
     93     private int mFullHighPerfLocksAcquired;
     94     private int mFullHighPerfLocksReleased;
     95     private int mFullLocksAcquired;
     96     private int mFullLocksReleased;
     97     private int mScanLocksAcquired;
     98     private int mScanLocksReleased;
     99 
    100     private final List<Multicaster> mMulticasters =
    101             new ArrayList<Multicaster>();
    102     private int mMulticastEnabled;
    103     private int mMulticastDisabled;
    104 
    105     private final IBatteryStats mBatteryStats;
    106     private final AppOpsManager mAppOps;
    107 
    108     private String mInterfaceName;
    109 
    110     /* Tracks the open wi-fi network notification */
    111     private WifiNotificationController mNotificationController;
    112     /* Polls traffic stats and notifies clients */
    113     private WifiTrafficPoller mTrafficPoller;
    114     /* Tracks the persisted states for wi-fi & airplane mode */
    115     final WifiSettingsStore mSettingsStore;
    116 
    117     /**
    118      * Asynchronous channel to WifiStateMachine
    119      */
    120     private AsyncChannel mWifiStateMachineChannel;
    121 
    122     /**
    123      * Handles client connections
    124      */
    125     private class ClientHandler extends Handler {
    126 
    127         ClientHandler(android.os.Looper looper) {
    128             super(looper);
    129         }
    130 
    131         @Override
    132         public void handleMessage(Message msg) {
    133             switch (msg.what) {
    134                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
    135                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
    136                         if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
    137                         // We track the clients by the Messenger
    138                         // since it is expected to be always available
    139                         mTrafficPoller.addClient(msg.replyTo);
    140                     } else {
    141                         Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
    142                     }
    143                     break;
    144                 }
    145                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
    146                     if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
    147                         if (DBG) Slog.d(TAG, "Send failed, client connection lost");
    148                     } else {
    149                         if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
    150                     }
    151                     mTrafficPoller.removeClient(msg.replyTo);
    152                     break;
    153                 }
    154                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
    155                     AsyncChannel ac = new AsyncChannel();
    156                     ac.connect(mContext, this, msg.replyTo);
    157                     break;
    158                 }
    159                 /* Client commands are forwarded to state machine */
    160                 case WifiManager.CONNECT_NETWORK:
    161                 case WifiManager.SAVE_NETWORK:
    162                 case WifiManager.FORGET_NETWORK:
    163                 case WifiManager.START_WPS:
    164                 case WifiManager.CANCEL_WPS:
    165                 case WifiManager.DISABLE_NETWORK:
    166                 case WifiManager.RSSI_PKTCNT_FETCH: {
    167                     mWifiStateMachine.sendMessage(Message.obtain(msg));
    168                     break;
    169                 }
    170                 default: {
    171                     Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
    172                     break;
    173                 }
    174             }
    175         }
    176     }
    177     private ClientHandler mClientHandler;
    178 
    179     /**
    180      * Handles interaction with WifiStateMachine
    181      */
    182     private class WifiStateMachineHandler extends Handler {
    183         private AsyncChannel mWsmChannel;
    184 
    185         WifiStateMachineHandler(android.os.Looper looper) {
    186             super(looper);
    187             mWsmChannel = new AsyncChannel();
    188             mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
    189         }
    190 
    191         @Override
    192         public void handleMessage(Message msg) {
    193             switch (msg.what) {
    194                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
    195                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
    196                         mWifiStateMachineChannel = mWsmChannel;
    197                     } else {
    198                         Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1);
    199                         mWifiStateMachineChannel = null;
    200                     }
    201                     break;
    202                 }
    203                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
    204                     Slog.e(TAG, "WifiStateMachine channel lost, msg.arg1 =" + msg.arg1);
    205                     mWifiStateMachineChannel = null;
    206                     //Re-establish connection to state machine
    207                     mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
    208                     break;
    209                 }
    210                 default: {
    211                     Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg);
    212                     break;
    213                 }
    214             }
    215         }
    216     }
    217     WifiStateMachineHandler mWifiStateMachineHandler;
    218 
    219     private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
    220 
    221     public WifiService(Context context) {
    222         mContext = context;
    223 
    224         mInterfaceName =  SystemProperties.get("wifi.interface", "wlan0");
    225 
    226         mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
    227         mWifiStateMachine.enableRssiPolling(true);
    228         mBatteryStats = BatteryStatsService.getService();
    229         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
    230 
    231         mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
    232         mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
    233         mSettingsStore = new WifiSettingsStore(mContext);
    234 
    235         HandlerThread wifiThread = new HandlerThread("WifiService");
    236         wifiThread.start();
    237         mClientHandler = new ClientHandler(wifiThread.getLooper());
    238         mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
    239         mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
    240         mWifiController.start();
    241 
    242         registerForScanModeChange();
    243         mContext.registerReceiver(
    244                 new BroadcastReceiver() {
    245                     @Override
    246                     public void onReceive(Context context, Intent intent) {
    247                         if (mSettingsStore.handleAirplaneModeToggled()) {
    248                             mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
    249                         }
    250                     }
    251                 },
    252                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
    253 
    254         // Adding optimizations of only receiving broadcasts when wifi is enabled
    255         // can result in race conditions when apps toggle wifi in the background
    256         // without active user involvement. Always receive broadcasts.
    257         registerForBroadcasts();
    258     }
    259 
    260     private WifiController mWifiController;
    261 
    262     /**
    263      * Check if Wi-Fi needs to be enabled and start
    264      * if needed
    265      *
    266      * This function is used only at boot time
    267      */
    268     public void checkAndStartWifi() {
    269         /* Check if wi-fi needs to be enabled */
    270         boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
    271         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
    272                 (wifiEnabled ? "enabled" : "disabled"));
    273 
    274         // If we are already disabled (could be due to airplane mode), avoid changing persist
    275         // state here
    276         if (wifiEnabled) setWifiEnabled(wifiEnabled);
    277 
    278         mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
    279                makeWifiWatchdogStateMachine(mContext);
    280 
    281     }
    282 
    283     /**
    284      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
    285      * @return {@code true} if the operation succeeds, {@code false} otherwise
    286      */
    287     public boolean pingSupplicant() {
    288         enforceAccessPermission();
    289         if (mWifiStateMachineChannel != null) {
    290             return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
    291         } else {
    292             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    293             return false;
    294         }
    295     }
    296 
    297     /**
    298      * see {@link android.net.wifi.WifiManager#startScan()}
    299      */
    300     public void startScan() {
    301         enforceChangePermission();
    302         mWifiStateMachine.startScan(Binder.getCallingUid());
    303     }
    304 
    305     private void enforceAccessPermission() {
    306         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
    307                                                 "WifiService");
    308     }
    309 
    310     private void enforceChangePermission() {
    311         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
    312                                                 "WifiService");
    313 
    314     }
    315 
    316     private void enforceMulticastChangePermission() {
    317         mContext.enforceCallingOrSelfPermission(
    318                 android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
    319                 "WifiService");
    320     }
    321 
    322     private void enforceConnectivityInternalPermission() {
    323         mContext.enforceCallingOrSelfPermission(
    324                 android.Manifest.permission.CONNECTIVITY_INTERNAL,
    325                 "ConnectivityService");
    326     }
    327 
    328     /**
    329      * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
    330      * @param enable {@code true} to enable, {@code false} to disable.
    331      * @return {@code true} if the enable/disable operation was
    332      *         started or is already in the queue.
    333      */
    334     public synchronized boolean setWifiEnabled(boolean enable) {
    335         enforceChangePermission();
    336         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
    337                     + ", uid=" + Binder.getCallingUid());
    338         if (DBG) {
    339             Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
    340         }
    341 
    342         /*
    343         * Caller might not have WRITE_SECURE_SETTINGS,
    344         * only CHANGE_WIFI_STATE is enforced
    345         */
    346 
    347         long ident = Binder.clearCallingIdentity();
    348         try {
    349             if (! mSettingsStore.handleWifiToggled(enable)) {
    350                 // Nothing to do if wifi cannot be toggled
    351                 return true;
    352             }
    353         } finally {
    354             Binder.restoreCallingIdentity(ident);
    355         }
    356 
    357         mWifiController.sendMessage(CMD_WIFI_TOGGLED);
    358         return true;
    359     }
    360 
    361     /**
    362      * see {@link WifiManager#getWifiState()}
    363      * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
    364      *         {@link WifiManager#WIFI_STATE_DISABLING},
    365      *         {@link WifiManager#WIFI_STATE_ENABLED},
    366      *         {@link WifiManager#WIFI_STATE_ENABLING},
    367      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
    368      */
    369     public int getWifiEnabledState() {
    370         enforceAccessPermission();
    371         return mWifiStateMachine.syncGetWifiState();
    372     }
    373 
    374     /**
    375      * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
    376      * @param wifiConfig SSID, security and channel details as
    377      *        part of WifiConfiguration
    378      * @param enabled true to enable and false to disable
    379      */
    380     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
    381         enforceChangePermission();
    382         mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
    383     }
    384 
    385     /**
    386      * see {@link WifiManager#getWifiApState()}
    387      * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
    388      *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
    389      *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
    390      *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
    391      *         {@link WifiManager#WIFI_AP_STATE_FAILED}
    392      */
    393     public int getWifiApEnabledState() {
    394         enforceAccessPermission();
    395         return mWifiStateMachine.syncGetWifiApState();
    396     }
    397 
    398     /**
    399      * see {@link WifiManager#getWifiApConfiguration()}
    400      * @return soft access point configuration
    401      */
    402     public WifiConfiguration getWifiApConfiguration() {
    403         enforceAccessPermission();
    404         return mWifiStateMachine.syncGetWifiApConfiguration();
    405     }
    406 
    407     /**
    408      * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
    409      * @param wifiConfig WifiConfiguration details for soft access point
    410      */
    411     public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
    412         enforceChangePermission();
    413         if (wifiConfig == null)
    414             return;
    415         mWifiStateMachine.setWifiApConfiguration(wifiConfig);
    416     }
    417 
    418     /**
    419      * @param enable {@code true} to enable, {@code false} to disable.
    420      * @return {@code true} if the enable/disable operation was
    421      *         started or is already in the queue.
    422      */
    423     public boolean isScanAlwaysAvailable() {
    424         enforceAccessPermission();
    425         return mSettingsStore.isScanAlwaysAvailable();
    426     }
    427 
    428     /**
    429      * see {@link android.net.wifi.WifiManager#disconnect()}
    430      */
    431     public void disconnect() {
    432         enforceChangePermission();
    433         mWifiStateMachine.disconnectCommand();
    434     }
    435 
    436     /**
    437      * see {@link android.net.wifi.WifiManager#reconnect()}
    438      */
    439     public void reconnect() {
    440         enforceChangePermission();
    441         mWifiStateMachine.reconnectCommand();
    442     }
    443 
    444     /**
    445      * see {@link android.net.wifi.WifiManager#reassociate()}
    446      */
    447     public void reassociate() {
    448         enforceChangePermission();
    449         mWifiStateMachine.reassociateCommand();
    450     }
    451 
    452     /**
    453      * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
    454      * @return the list of configured networks
    455      */
    456     public List<WifiConfiguration> getConfiguredNetworks() {
    457         enforceAccessPermission();
    458         if (mWifiStateMachineChannel != null) {
    459             return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel);
    460         } else {
    461             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    462             return null;
    463         }
    464     }
    465 
    466     /**
    467      * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
    468      * @return the supplicant-assigned identifier for the new or updated
    469      * network if the operation succeeds, or {@code -1} if it fails
    470      */
    471     public int addOrUpdateNetwork(WifiConfiguration config) {
    472         enforceChangePermission();
    473         if (mWifiStateMachineChannel != null) {
    474             return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
    475         } else {
    476             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    477             return -1;
    478         }
    479     }
    480 
    481      /**
    482      * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
    483      * @param netId the integer that identifies the network configuration
    484      * to the supplicant
    485      * @return {@code true} if the operation succeeded
    486      */
    487     public boolean removeNetwork(int netId) {
    488         enforceChangePermission();
    489         if (mWifiStateMachineChannel != null) {
    490             return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
    491         } else {
    492             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    493             return false;
    494         }
    495     }
    496 
    497     /**
    498      * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
    499      * @param netId the integer that identifies the network configuration
    500      * to the supplicant
    501      * @param disableOthers if true, disable all other networks.
    502      * @return {@code true} if the operation succeeded
    503      */
    504     public boolean enableNetwork(int netId, boolean disableOthers) {
    505         enforceChangePermission();
    506         if (mWifiStateMachineChannel != null) {
    507             return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
    508                     disableOthers);
    509         } else {
    510             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    511             return false;
    512         }
    513     }
    514 
    515     /**
    516      * See {@link android.net.wifi.WifiManager#disableNetwork(int)}
    517      * @param netId the integer that identifies the network configuration
    518      * to the supplicant
    519      * @return {@code true} if the operation succeeded
    520      */
    521     public boolean disableNetwork(int netId) {
    522         enforceChangePermission();
    523         if (mWifiStateMachineChannel != null) {
    524             return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
    525         } else {
    526             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    527             return false;
    528         }
    529     }
    530 
    531     /**
    532      * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
    533      * @return the Wi-Fi information, contained in {@link WifiInfo}.
    534      */
    535     public WifiInfo getConnectionInfo() {
    536         enforceAccessPermission();
    537         /*
    538          * Make sure we have the latest information, by sending
    539          * a status request to the supplicant.
    540          */
    541         return mWifiStateMachine.syncRequestConnectionInfo();
    542     }
    543 
    544     /**
    545      * Return the results of the most recent access point scan, in the form of
    546      * a list of {@link ScanResult} objects.
    547      * @return the list of results
    548      */
    549     public List<ScanResult> getScanResults(String callingPackage) {
    550         enforceAccessPermission();
    551         int userId = UserHandle.getCallingUserId();
    552         int uid = Binder.getCallingUid();
    553         long ident = Binder.clearCallingIdentity();
    554         if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
    555                 != AppOpsManager.MODE_ALLOWED) {
    556             return new ArrayList<ScanResult>();
    557         }
    558         try {
    559             int currentUser = ActivityManager.getCurrentUser();
    560             if (userId != currentUser) {
    561                 return new ArrayList<ScanResult>();
    562             } else {
    563                 return mWifiStateMachine.syncGetScanResultsList();
    564             }
    565         } finally {
    566             Binder.restoreCallingIdentity(ident);
    567         }
    568     }
    569 
    570     /**
    571      * Tell the supplicant to persist the current list of configured networks.
    572      * @return {@code true} if the operation succeeded
    573      *
    574      * TODO: deprecate this
    575      */
    576     public boolean saveConfiguration() {
    577         boolean result = true;
    578         enforceChangePermission();
    579         if (mWifiStateMachineChannel != null) {
    580             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
    581         } else {
    582             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
    583             return false;
    584         }
    585     }
    586 
    587     /**
    588      * Set the country code
    589      * @param countryCode ISO 3166 country code.
    590      * @param persist {@code true} if the setting should be remembered.
    591      *
    592      * The persist behavior exists so that wifi can fall back to the last
    593      * persisted country code on a restart, when the locale information is
    594      * not available from telephony.
    595      */
    596     public void setCountryCode(String countryCode, boolean persist) {
    597         Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
    598                 " with persist set to " + persist);
    599         enforceChangePermission();
    600         final long token = Binder.clearCallingIdentity();
    601         try {
    602             mWifiStateMachine.setCountryCode(countryCode, persist);
    603         } finally {
    604             Binder.restoreCallingIdentity(token);
    605         }
    606     }
    607 
    608     /**
    609      * Set the operational frequency band
    610      * @param band One of
    611      *     {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
    612      *     {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
    613      *     {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
    614      * @param persist {@code true} if the setting should be remembered.
    615      *
    616      */
    617     public void setFrequencyBand(int band, boolean persist) {
    618         enforceChangePermission();
    619         if (!isDualBandSupported()) return;
    620         Slog.i(TAG, "WifiService trying to set frequency band to " + band +
    621                 " with persist set to " + persist);
    622         final long token = Binder.clearCallingIdentity();
    623         try {
    624             mWifiStateMachine.setFrequencyBand(band, persist);
    625         } finally {
    626             Binder.restoreCallingIdentity(token);
    627         }
    628     }
    629 
    630 
    631     /**
    632      * Get the operational frequency band
    633      */
    634     public int getFrequencyBand() {
    635         enforceAccessPermission();
    636         return mWifiStateMachine.getFrequencyBand();
    637     }
    638 
    639     public boolean isDualBandSupported() {
    640         //TODO: Should move towards adding a driver API that checks at runtime
    641         return mContext.getResources().getBoolean(
    642                 com.android.internal.R.bool.config_wifi_dual_band_support);
    643     }
    644 
    645     /**
    646      * Return the DHCP-assigned addresses from the last successful DHCP request,
    647      * if any.
    648      * @return the DHCP information
    649      * @deprecated
    650      */
    651     public DhcpInfo getDhcpInfo() {
    652         enforceAccessPermission();
    653         DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
    654         if (dhcpResults.linkProperties == null) return null;
    655 
    656         DhcpInfo info = new DhcpInfo();
    657         for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
    658             InetAddress addr = la.getAddress();
    659             if (addr instanceof Inet4Address) {
    660                 info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
    661                 break;
    662             }
    663         }
    664         for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
    665             if (r.isDefaultRoute()) {
    666                 InetAddress gateway = r.getGateway();
    667                 if (gateway instanceof Inet4Address) {
    668                     info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
    669                 }
    670             } else if (r.hasGateway() == false) {
    671                 LinkAddress dest = r.getDestination();
    672                 if (dest.getAddress() instanceof Inet4Address) {
    673                     info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
    674                             dest.getNetworkPrefixLength());
    675                 }
    676             }
    677         }
    678         int dnsFound = 0;
    679         for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
    680             if (dns instanceof Inet4Address) {
    681                 if (dnsFound == 0) {
    682                     info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
    683                 } else {
    684                     info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
    685                 }
    686                 if (++dnsFound > 1) break;
    687             }
    688         }
    689         InetAddress serverAddress = dhcpResults.serverAddress;
    690         if (serverAddress instanceof Inet4Address) {
    691             info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
    692         }
    693         info.leaseDuration = dhcpResults.leaseDuration;
    694 
    695         return info;
    696     }
    697 
    698     /**
    699      * see {@link android.net.wifi.WifiManager#startWifi}
    700      *
    701      */
    702     public void startWifi() {
    703         enforceConnectivityInternalPermission();
    704         /* TODO: may be add permissions for access only to connectivity service
    705          * TODO: if a start issued, keep wifi alive until a stop issued irrespective
    706          * of WifiLock & device idle status unless wifi enabled status is toggled
    707          */
    708 
    709         mWifiStateMachine.setDriverStart(true);
    710         mWifiStateMachine.reconnectCommand();
    711     }
    712 
    713     public void captivePortalCheckComplete() {
    714         enforceConnectivityInternalPermission();
    715         mWifiStateMachine.captivePortalCheckComplete();
    716     }
    717 
    718     /**
    719      * see {@link android.net.wifi.WifiManager#stopWifi}
    720      *
    721      */
    722     public void stopWifi() {
    723         enforceConnectivityInternalPermission();
    724         /*
    725          * TODO: if a stop is issued, wifi is brought up only by startWifi
    726          * unless wifi enabled status is toggled
    727          */
    728         mWifiStateMachine.setDriverStart(false);
    729     }
    730 
    731     /**
    732      * see {@link android.net.wifi.WifiManager#addToBlacklist}
    733      *
    734      */
    735     public void addToBlacklist(String bssid) {
    736         enforceChangePermission();
    737 
    738         mWifiStateMachine.addToBlacklist(bssid);
    739     }
    740 
    741     /**
    742      * see {@link android.net.wifi.WifiManager#clearBlacklist}
    743      *
    744      */
    745     public void clearBlacklist() {
    746         enforceChangePermission();
    747 
    748         mWifiStateMachine.clearBlacklist();
    749     }
    750 
    751     /**
    752      * Get a reference to handler. This is used by a client to establish
    753      * an AsyncChannel communication with WifiService
    754      */
    755     public Messenger getWifiServiceMessenger() {
    756         enforceAccessPermission();
    757         enforceChangePermission();
    758         return new Messenger(mClientHandler);
    759     }
    760 
    761     /** Get a reference to WifiStateMachine handler for AsyncChannel communication */
    762     public Messenger getWifiStateMachineMessenger() {
    763         enforceAccessPermission();
    764         enforceChangePermission();
    765         return mWifiStateMachine.getMessenger();
    766     }
    767 
    768     /**
    769      * Get the IP and proxy configuration file
    770      */
    771     public String getConfigFile() {
    772         enforceAccessPermission();
    773         return mWifiStateMachine.getConfigFile();
    774     }
    775 
    776     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    777         @Override
    778         public void onReceive(Context context, Intent intent) {
    779             String action = intent.getAction();
    780             if (action.equals(Intent.ACTION_SCREEN_ON)) {
    781                 mWifiController.sendMessage(CMD_SCREEN_ON);
    782             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
    783                 mWifiController.sendMessage(CMD_SCREEN_OFF);
    784             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
    785                 int pluggedType = intent.getIntExtra("plugged", 0);
    786                 mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
    787             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
    788                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
    789                         BluetoothAdapter.STATE_DISCONNECTED);
    790                 mWifiStateMachine.sendBluetoothAdapterStateChange(state);
    791             } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
    792                 boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
    793                 mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
    794             }
    795         }
    796     };
    797 
    798     /**
    799      * Observes settings changes to scan always mode.
    800      */
    801     private void registerForScanModeChange() {
    802         ContentObserver contentObserver = new ContentObserver(null) {
    803             @Override
    804             public void onChange(boolean selfChange) {
    805                 mSettingsStore.handleWifiScanAlwaysAvailableToggled();
    806                 mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
    807             }
    808         };
    809 
    810         mContext.getContentResolver().registerContentObserver(
    811                 Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
    812                 false, contentObserver);
    813     }
    814 
    815     private void registerForBroadcasts() {
    816         IntentFilter intentFilter = new IntentFilter();
    817         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
    818         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
    819         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
    820         intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    821         intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
    822         intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
    823         mContext.registerReceiver(mReceiver, intentFilter);
    824     }
    825 
    826     @Override
    827     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    828         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    829                 != PackageManager.PERMISSION_GRANTED) {
    830             pw.println("Permission Denial: can't dump WifiService from from pid="
    831                     + Binder.getCallingPid()
    832                     + ", uid=" + Binder.getCallingUid());
    833             return;
    834         }
    835         pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
    836         pw.println("Stay-awake conditions: " +
    837                 Settings.Global.getInt(mContext.getContentResolver(),
    838                                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
    839         pw.println("mMulticastEnabled " + mMulticastEnabled);
    840         pw.println("mMulticastDisabled " + mMulticastDisabled);
    841         mWifiController.dump(fd, pw, args);
    842         mSettingsStore.dump(fd, pw, args);
    843         mNotificationController.dump(fd, pw, args);
    844         mTrafficPoller.dump(fd, pw, args);
    845 
    846         pw.println("Latest scan results:");
    847         List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
    848         if (scanResults != null && scanResults.size() != 0) {
    849             pw.println("  BSSID              Frequency   RSSI  Flags             SSID");
    850             for (ScanResult r : scanResults) {
    851                 pw.printf("  %17s  %9d  %5d  %-16s  %s%n",
    852                                          r.BSSID,
    853                                          r.frequency,
    854                                          r.level,
    855                                          r.capabilities,
    856                                          r.SSID == null ? "" : r.SSID);
    857             }
    858         }
    859         pw.println();
    860         pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
    861                 mFullHighPerfLocksAcquired + " full high perf, " +
    862                 mScanLocksAcquired + " scan");
    863         pw.println("Locks released: " + mFullLocksReleased + " full, " +
    864                 mFullHighPerfLocksReleased + " full high perf, " +
    865                 mScanLocksReleased + " scan");
    866         pw.println();
    867         pw.println("Locks held:");
    868         mLocks.dump(pw);
    869 
    870         mWifiWatchdogStateMachine.dump(fd, pw, args);
    871         pw.println();
    872         mWifiStateMachine.dump(fd, pw, args);
    873         pw.println();
    874     }
    875 
    876     private class WifiLock extends DeathRecipient {
    877         WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
    878             super(lockMode, tag, binder, ws);
    879         }
    880 
    881         public void binderDied() {
    882             synchronized (mLocks) {
    883                 releaseWifiLockLocked(mBinder);
    884             }
    885         }
    886 
    887         public String toString() {
    888             return "WifiLock{" + mTag + " type=" + mMode + " binder=" + mBinder + "}";
    889         }
    890     }
    891 
    892     class LockList {
    893         private List<WifiLock> mList;
    894 
    895         private LockList() {
    896             mList = new ArrayList<WifiLock>();
    897         }
    898 
    899         synchronized boolean hasLocks() {
    900             return !mList.isEmpty();
    901         }
    902 
    903         synchronized int getStrongestLockMode() {
    904             if (mList.isEmpty()) {
    905                 return WifiManager.WIFI_MODE_FULL;
    906             }
    907 
    908             if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
    909                 return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
    910             }
    911 
    912             if (mFullLocksAcquired > mFullLocksReleased) {
    913                 return WifiManager.WIFI_MODE_FULL;
    914             }
    915 
    916             return WifiManager.WIFI_MODE_SCAN_ONLY;
    917         }
    918 
    919         synchronized void updateWorkSource(WorkSource ws) {
    920             for (int i = 0; i < mLocks.mList.size(); i++) {
    921                 ws.add(mLocks.mList.get(i).mWorkSource);
    922             }
    923         }
    924 
    925         private void addLock(WifiLock lock) {
    926             if (findLockByBinder(lock.mBinder) < 0) {
    927                 mList.add(lock);
    928             }
    929         }
    930 
    931         private WifiLock removeLock(IBinder binder) {
    932             int index = findLockByBinder(binder);
    933             if (index >= 0) {
    934                 WifiLock ret = mList.remove(index);
    935                 ret.unlinkDeathRecipient();
    936                 return ret;
    937             } else {
    938                 return null;
    939             }
    940         }
    941 
    942         private int findLockByBinder(IBinder binder) {
    943             int size = mList.size();
    944             for (int i = size - 1; i >= 0; i--) {
    945                 if (mList.get(i).mBinder == binder)
    946                     return i;
    947             }
    948             return -1;
    949         }
    950 
    951         private void dump(PrintWriter pw) {
    952             for (WifiLock l : mList) {
    953                 pw.print("    ");
    954                 pw.println(l);
    955             }
    956         }
    957     }
    958 
    959     void enforceWakeSourcePermission(int uid, int pid) {
    960         if (uid == android.os.Process.myUid()) {
    961             return;
    962         }
    963         mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
    964                 pid, uid, null);
    965     }
    966 
    967     public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
    968         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
    969         if (lockMode != WifiManager.WIFI_MODE_FULL &&
    970                 lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
    971                 lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
    972             Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
    973             if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
    974             return false;
    975         }
    976         if (ws != null && ws.size() == 0) {
    977             ws = null;
    978         }
    979         if (ws != null) {
    980             enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
    981         }
    982         if (ws == null) {
    983             ws = new WorkSource(Binder.getCallingUid());
    984         }
    985         WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
    986         synchronized (mLocks) {
    987             return acquireWifiLockLocked(wifiLock);
    988         }
    989     }
    990 
    991     private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
    992         switch(wifiLock.mMode) {
    993             case WifiManager.WIFI_MODE_FULL:
    994             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
    995             case WifiManager.WIFI_MODE_SCAN_ONLY:
    996                 mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
    997                 break;
    998         }
    999     }
   1000 
   1001     private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
   1002         switch(wifiLock.mMode) {
   1003             case WifiManager.WIFI_MODE_FULL:
   1004             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
   1005             case WifiManager.WIFI_MODE_SCAN_ONLY:
   1006                 mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
   1007                 break;
   1008         }
   1009     }
   1010 
   1011     private boolean acquireWifiLockLocked(WifiLock wifiLock) {
   1012         if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
   1013 
   1014         mLocks.addLock(wifiLock);
   1015 
   1016         long ident = Binder.clearCallingIdentity();
   1017         try {
   1018             noteAcquireWifiLock(wifiLock);
   1019             switch(wifiLock.mMode) {
   1020             case WifiManager.WIFI_MODE_FULL:
   1021                 ++mFullLocksAcquired;
   1022                 break;
   1023             case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
   1024                 ++mFullHighPerfLocksAcquired;
   1025                 break;
   1026 
   1027             case WifiManager.WIFI_MODE_SCAN_ONLY:
   1028                 ++mScanLocksAcquired;
   1029                 break;
   1030             }
   1031             mWifiController.sendMessage(CMD_LOCKS_CHANGED);
   1032             return true;
   1033         } catch (RemoteException e) {
   1034             return false;
   1035         } finally {
   1036             Binder.restoreCallingIdentity(ident);
   1037         }
   1038     }
   1039 
   1040     public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
   1041         int uid = Binder.getCallingUid();
   1042         int pid = Binder.getCallingPid();
   1043         if (ws != null && ws.size() == 0) {
   1044             ws = null;
   1045         }
   1046         if (ws != null) {
   1047             enforceWakeSourcePermission(uid, pid);
   1048         }
   1049         long ident = Binder.clearCallingIdentity();
   1050         try {
   1051             synchronized (mLocks) {
   1052                 int index = mLocks.findLockByBinder(lock);
   1053                 if (index < 0) {
   1054                     throw new IllegalArgumentException("Wifi lock not active");
   1055                 }
   1056                 WifiLock wl = mLocks.mList.get(index);
   1057                 noteReleaseWifiLock(wl);
   1058                 wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
   1059                 noteAcquireWifiLock(wl);
   1060             }
   1061         } catch (RemoteException e) {
   1062         } finally {
   1063             Binder.restoreCallingIdentity(ident);
   1064         }
   1065     }
   1066 
   1067     public boolean releaseWifiLock(IBinder lock) {
   1068         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
   1069         synchronized (mLocks) {
   1070             return releaseWifiLockLocked(lock);
   1071         }
   1072     }
   1073 
   1074     private boolean releaseWifiLockLocked(IBinder lock) {
   1075         boolean hadLock;
   1076 
   1077         WifiLock wifiLock = mLocks.removeLock(lock);
   1078 
   1079         if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
   1080 
   1081         hadLock = (wifiLock != null);
   1082 
   1083         long ident = Binder.clearCallingIdentity();
   1084         try {
   1085             if (hadLock) {
   1086                 noteReleaseWifiLock(wifiLock);
   1087                 switch(wifiLock.mMode) {
   1088                     case WifiManager.WIFI_MODE_FULL:
   1089                         ++mFullLocksReleased;
   1090                         break;
   1091                     case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
   1092                         ++mFullHighPerfLocksReleased;
   1093                         break;
   1094                     case WifiManager.WIFI_MODE_SCAN_ONLY:
   1095                         ++mScanLocksReleased;
   1096                         break;
   1097                 }
   1098                 mWifiController.sendMessage(CMD_LOCKS_CHANGED);
   1099             }
   1100         } catch (RemoteException e) {
   1101         } finally {
   1102             Binder.restoreCallingIdentity(ident);
   1103         }
   1104 
   1105         return hadLock;
   1106     }
   1107 
   1108     private abstract class DeathRecipient
   1109             implements IBinder.DeathRecipient {
   1110         String mTag;
   1111         int mMode;
   1112         IBinder mBinder;
   1113         WorkSource mWorkSource;
   1114 
   1115         DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
   1116             super();
   1117             mTag = tag;
   1118             mMode = mode;
   1119             mBinder = binder;
   1120             mWorkSource = ws;
   1121             try {
   1122                 mBinder.linkToDeath(this, 0);
   1123             } catch (RemoteException e) {
   1124                 binderDied();
   1125             }
   1126         }
   1127 
   1128         void unlinkDeathRecipient() {
   1129             mBinder.unlinkToDeath(this, 0);
   1130         }
   1131     }
   1132 
   1133     private class Multicaster extends DeathRecipient {
   1134         Multicaster(String tag, IBinder binder) {
   1135             super(Binder.getCallingUid(), tag, binder, null);
   1136         }
   1137 
   1138         public void binderDied() {
   1139             Slog.e(TAG, "Multicaster binderDied");
   1140             synchronized (mMulticasters) {
   1141                 int i = mMulticasters.indexOf(this);
   1142                 if (i != -1) {
   1143                     removeMulticasterLocked(i, mMode);
   1144                 }
   1145             }
   1146         }
   1147 
   1148         public String toString() {
   1149             return "Multicaster{" + mTag + " binder=" + mBinder + "}";
   1150         }
   1151 
   1152         public int getUid() {
   1153             return mMode;
   1154         }
   1155     }
   1156 
   1157     public void initializeMulticastFiltering() {
   1158         enforceMulticastChangePermission();
   1159 
   1160         synchronized (mMulticasters) {
   1161             // if anybody had requested filters be off, leave off
   1162             if (mMulticasters.size() != 0) {
   1163                 return;
   1164             } else {
   1165                 mWifiStateMachine.startFilteringMulticastV4Packets();
   1166             }
   1167         }
   1168     }
   1169 
   1170     public void acquireMulticastLock(IBinder binder, String tag) {
   1171         enforceMulticastChangePermission();
   1172 
   1173         synchronized (mMulticasters) {
   1174             mMulticastEnabled++;
   1175             mMulticasters.add(new Multicaster(tag, binder));
   1176             // Note that we could call stopFilteringMulticastV4Packets only when
   1177             // our new size == 1 (first call), but this function won't
   1178             // be called often and by making the stopPacket call each
   1179             // time we're less fragile and self-healing.
   1180             mWifiStateMachine.stopFilteringMulticastV4Packets();
   1181         }
   1182 
   1183         int uid = Binder.getCallingUid();
   1184         final long ident = Binder.clearCallingIdentity();
   1185         try {
   1186             mBatteryStats.noteWifiMulticastEnabled(uid);
   1187         } catch (RemoteException e) {
   1188         } finally {
   1189             Binder.restoreCallingIdentity(ident);
   1190         }
   1191     }
   1192 
   1193     public void releaseMulticastLock() {
   1194         enforceMulticastChangePermission();
   1195 
   1196         int uid = Binder.getCallingUid();
   1197         synchronized (mMulticasters) {
   1198             mMulticastDisabled++;
   1199             int size = mMulticasters.size();
   1200             for (int i = size - 1; i >= 0; i--) {
   1201                 Multicaster m = mMulticasters.get(i);
   1202                 if ((m != null) && (m.getUid() == uid)) {
   1203                     removeMulticasterLocked(i, uid);
   1204                 }
   1205             }
   1206         }
   1207     }
   1208 
   1209     private void removeMulticasterLocked(int i, int uid)
   1210     {
   1211         Multicaster removed = mMulticasters.remove(i);
   1212 
   1213         if (removed != null) {
   1214             removed.unlinkDeathRecipient();
   1215         }
   1216         if (mMulticasters.size() == 0) {
   1217             mWifiStateMachine.startFilteringMulticastV4Packets();
   1218         }
   1219 
   1220         final long ident = Binder.clearCallingIdentity();
   1221         try {
   1222             mBatteryStats.noteWifiMulticastDisabled(uid);
   1223         } catch (RemoteException e) {
   1224         } finally {
   1225             Binder.restoreCallingIdentity(ident);
   1226         }
   1227     }
   1228 
   1229     public boolean isMulticastEnabled() {
   1230         enforceAccessPermission();
   1231 
   1232         synchronized (mMulticasters) {
   1233             return (mMulticasters.size() > 0);
   1234         }
   1235     }
   1236 }
   1237