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