WifiNative.java revision 368b164d5511e105c95d490e250640edd61045d0
1/*
2 * Copyright (C) 2008 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.net.wifi.BatchedScanSettings;
20import android.net.wifi.ScanResult;
21import android.net.wifi.WifiLinkLayerStats;
22import android.net.wifi.WifiScanner;
23import android.net.wifi.WpsInfo;
24import android.net.wifi.p2p.WifiP2pConfig;
25import android.net.wifi.p2p.WifiP2pGroup;
26import android.os.SystemClock;
27import android.text.TextUtils;
28import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
29import android.util.LocalLog;
30import android.util.Log;
31
32import java.util.ArrayList;
33import java.util.List;
34import java.util.Locale;
35
36/**
37 * Native calls for bring up/shut down of the supplicant daemon and for
38 * sending requests to the supplicant daemon
39 *
40 * waitForEvent() is called on the monitor thread for events. All other methods
41 * must be serialized from the framework.
42 *
43 * {@hide}
44 */
45public class WifiNative {
46
47    private static boolean DBG = false;
48    private final String mTAG;
49    private static final int DEFAULT_GROUP_OWNER_INTENT     = 6;
50
51    static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED     = 0;
52    static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED    = 1;
53    static final int BLUETOOTH_COEXISTENCE_MODE_SENSE       = 2;
54
55    static final int SCAN_WITHOUT_CONNECTION_SETUP          = 1;
56    static final int SCAN_WITH_CONNECTION_SETUP             = 2;
57
58    // Hold this lock before calling supplicant - it is required to
59    // mutually exclude access from Wifi and P2p state machines
60    static final Object mLock = new Object();
61
62    public final String mInterfaceName;
63    public final String mInterfacePrefix;
64
65    private boolean mSuspendOptEnabled = false;
66
67    /* Register native functions */
68
69    static {
70        /* Native functions are defined in libwifi-service.so */
71        System.loadLibrary("wifi-service");
72        registerNatives();
73    }
74
75    private static native int registerNatives();
76
77    public native static boolean loadDriver();
78
79    public native static boolean isDriverLoaded();
80
81    public native static boolean unloadDriver();
82
83    public native static boolean startSupplicant(boolean p2pSupported);
84
85    /* Sends a kill signal to supplicant. To be used when we have lost connection
86       or when the supplicant is hung */
87    public native static boolean killSupplicant(boolean p2pSupported);
88
89    private native boolean connectToSupplicantNative();
90
91    private native void closeSupplicantConnectionNative();
92
93    /**
94     * Wait for the supplicant to send an event, returning the event string.
95     * @return the event string sent by the supplicant.
96     */
97    private native String waitForEventNative();
98
99    private native boolean doBooleanCommandNative(String command);
100
101    private native int doIntCommandNative(String command);
102
103    private native String doStringCommandNative(String command);
104
105    public WifiNative(String interfaceName) {
106        mInterfaceName = interfaceName;
107        mTAG = "WifiNative-" + interfaceName;
108        if (!interfaceName.equals("p2p0")) {
109            mInterfacePrefix = "IFNAME=" + interfaceName + " ";
110        } else {
111            // commands for p2p0 interface don't need prefix
112            mInterfacePrefix = "";
113        }
114    }
115
116    void enableVerboseLogging(int verbose) {
117        if (verbose > 0) {
118            DBG = true;
119        } else {
120            DBG = false;
121        }
122    }
123
124    private static final LocalLog mLocalLog = new LocalLog(1024);
125
126    // hold mLock before accessing mCmdIdLock
127    private static int sCmdId;
128
129    public LocalLog getLocalLog() {
130        return mLocalLog;
131    }
132
133    private static int getNewCmdIdLocked() {
134        return sCmdId++;
135    }
136
137    private void localLog(String s) {
138        if (mLocalLog != null)
139            mLocalLog.log(mInterfaceName + ": " + s);
140    }
141
142    public boolean connectToSupplicant() {
143        // No synchronization necessary .. it is implemented in WifiMonitor
144        localLog(mInterfacePrefix + "connectToSupplicant");
145        return connectToSupplicantNative();
146    }
147
148    public void closeSupplicantConnection() {
149        localLog(mInterfacePrefix + "closeSupplicantConnection");
150        closeSupplicantConnectionNative();
151    }
152
153    public String waitForEvent() {
154        // No synchronization necessary .. it is implemented in WifiMonitor
155        return waitForEventNative();
156    }
157
158    private boolean doBooleanCommand(String command) {
159        if (DBG) Log.d(mTAG, "doBoolean: " + command);
160        synchronized (mLock) {
161            int cmdId = getNewCmdIdLocked();
162            localLog(cmdId + "->" + mInterfacePrefix + command);
163            boolean result = doBooleanCommandNative(mInterfacePrefix + command);
164            localLog(cmdId + "<-" + result);
165            if (DBG) Log.d(mTAG, "   returned " + result);
166            return result;
167        }
168    }
169
170    private int doIntCommand(String command) {
171        if (DBG) Log.d(mTAG, "doInt: " + command);
172        synchronized (mLock) {
173            int cmdId = getNewCmdIdLocked();
174            localLog(cmdId + "->" + mInterfacePrefix + command);
175            int result = doIntCommandNative(mInterfacePrefix + command);
176            localLog(cmdId + "<-" + result);
177            if (DBG) Log.d(mTAG, "   returned " + result);
178            return result;
179        }
180    }
181
182    private String doStringCommand(String command) {
183        if (DBG) Log.d(mTAG, "doString: " + command);
184        synchronized (mLock) {
185            int cmdId = getNewCmdIdLocked();
186            localLog(cmdId + "->" + mInterfacePrefix + command);
187            String result = doStringCommandNative(mInterfacePrefix + command);
188            localLog(cmdId + "<-" + result);
189            if (DBG) Log.d(mTAG, "   returned " + result);
190            return result;
191        }
192    }
193
194    private String doStringCommandWithoutLogging(String command) {
195        if (DBG) Log.d(mTAG, "doString: " + command);
196        synchronized (mLock) {
197            return doStringCommandNative(mInterfacePrefix + command);
198        }
199    }
200
201    public boolean ping() {
202        String pong = doStringCommand("PING");
203        return (pong != null && pong.equals("PONG"));
204    }
205
206    public String getFreqCapability() {
207        return doStringCommand("GET_CAPABILITY freq");
208    }
209
210    public boolean scan(int type, String freqList) {
211        if (type == SCAN_WITHOUT_CONNECTION_SETUP) {
212            if (freqList == null) return doBooleanCommand("SCAN TYPE=ONLY");
213            else return doBooleanCommand("SCAN TYPE=ONLY freq=" + freqList);
214        } else if (type == SCAN_WITH_CONNECTION_SETUP) {
215            if (freqList == null) return doBooleanCommand("SCAN");
216            else return doBooleanCommand("SCAN freq=" + freqList);
217        } else {
218            throw new IllegalArgumentException("Invalid scan type");
219        }
220    }
221
222    /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta.
223     *
224     * Note that underneath we use a harsh-sounding "terminate" supplicant command
225     * for a graceful stop and a mild-sounding "stop" interface
226     * to kill the process
227     */
228    public boolean stopSupplicant() {
229        return doBooleanCommand("TERMINATE");
230    }
231
232    public String listNetworks() {
233        return doStringCommand("LIST_NETWORKS");
234    }
235
236    public int addNetwork() {
237        return doIntCommand("ADD_NETWORK");
238    }
239
240    public boolean setNetworkVariable(int netId, String name, String value) {
241        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
242        return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
243    }
244
245    public String getNetworkVariable(int netId, String name) {
246        if (TextUtils.isEmpty(name)) return null;
247
248        // GET_NETWORK will likely flood the logs ...
249        return doStringCommandWithoutLogging("GET_NETWORK " + netId + " " + name);
250    }
251
252    public boolean removeNetwork(int netId) {
253        return doBooleanCommand("REMOVE_NETWORK " + netId);
254    }
255
256
257    private void logDbg(String debug) {
258        long now = SystemClock.elapsedRealtimeNanos();
259        String ts = String.format("[%,d us] ", now/1000);
260        Log.e("WifiNative: ", ts+debug+ " stack:"
261                + Thread.currentThread().getStackTrace()[2].getMethodName() +" - "
262                + Thread.currentThread().getStackTrace()[3].getMethodName() +" - "
263                + Thread.currentThread().getStackTrace()[4].getMethodName() +" - "
264                + Thread.currentThread().getStackTrace()[5].getMethodName()+" - "
265                + Thread.currentThread().getStackTrace()[6].getMethodName());
266
267    }
268    public boolean enableNetwork(int netId, boolean disableOthers) {
269        if (DBG) logDbg("enableNetwork nid=" + Integer.toString(netId)
270                + " disableOthers=" + disableOthers);
271        if (disableOthers) {
272            return doBooleanCommand("SELECT_NETWORK " + netId);
273        } else {
274            return doBooleanCommand("ENABLE_NETWORK " + netId);
275        }
276    }
277
278    public boolean disableNetwork(int netId) {
279        if (DBG) logDbg("disableNetwork nid=" + Integer.toString(netId));
280        return doBooleanCommand("DISABLE_NETWORK " + netId);
281    }
282
283    public boolean reconnect() {
284        if (DBG) logDbg("RECONNECT ");
285        return doBooleanCommand("RECONNECT");
286    }
287
288    public boolean reassociate() {
289        if (DBG) logDbg("REASSOCIATE ");
290        return doBooleanCommand("REASSOCIATE");
291    }
292
293    public boolean disconnect() {
294        if (DBG) logDbg("RECONNECT ");
295        return doBooleanCommand("DISCONNECT");
296    }
297
298    public String status() {
299        return doStringCommand("STATUS");
300    }
301
302    public String getMacAddress() {
303        //Macaddr = XX.XX.XX.XX.XX.XX
304        String ret = doStringCommand("DRIVER MACADDR");
305        if (!TextUtils.isEmpty(ret)) {
306            String[] tokens = ret.split(" = ");
307            if (tokens.length == 2) return tokens[1];
308        }
309        return null;
310    }
311
312    /**
313     * Format of results:
314     * =================
315     * id=1
316     * bssid=68:7f:74:d7:1b:6e
317     * freq=2412
318     * level=-43
319     * tsf=1344621975160944
320     * age=2623
321     * flags=[WPA2-PSK-CCMP][WPS][ESS]
322     * ssid=zubyb
323     * ====
324     *
325     * RANGE=ALL gets all scan results
326     * RANGE=ID- gets results from ID
327     * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
328     */
329    public String scanResults(int sid) {
330        return doStringCommandWithoutLogging("BSS RANGE=" + sid + "- MASK=0x21987");
331    }
332
333    /**
334     * Format of result:
335     * id=1016
336     * bssid=00:03:7f:40:84:10
337     * freq=2462
338     * beacon_int=200
339     * capabilities=0x0431
340     * qual=0
341     * noise=0
342     * level=-46
343     * tsf=0000002669008476
344     * age=5
345     * ie=00105143412d485332302d52322d54455354010882848b960c12182403010b0706555...
346     * flags=[WPA2-EAP-CCMP][ESS][P2P][HS20]
347     * ssid=QCA-HS20-R2-TEST
348     * p2p_device_name=
349     * p2p_config_methods=0x0
350     * anqp_venue_name=02083d656e6757692d466920416c6c69616e63650a3239383920436f...
351     * anqp_network_auth_type=010000
352     * anqp_roaming_consortium=03506f9a05001bc504bd
353     * anqp_ip_addr_type_availability=0c
354     * anqp_nai_realm=0200300000246d61696c2e6578616d706c652e636f6d3b636973636f2...
355     * anqp_3gpp=000600040132f465
356     * anqp_domain_name=0b65786d61706c652e636f6d
357     * hs20_operator_friendly_name=11656e6757692d466920416c6c69616e63650e636869...
358     * hs20_wan_metrics=01c40900008001000000000a00
359     * hs20_connection_capability=0100000006140001061600000650000106bb010106bb0...
360     * hs20_osu_providers_list=0b5143412d4f53552d425353010901310015656e6757692d...
361     */
362    public String scanResult(String bssid) {
363        return doStringCommand("BSS " + bssid);
364    }
365
366    /**
367     * Format of command
368     * DRIVER WLS_BATCHING SET SCANFREQ=x MSCAN=r BESTN=y CHANNEL=<z, w, t> RTT=s
369     * where x is an ascii representation of an integer number of seconds between scans
370     *       r is an ascii representation of an integer number of scans per batch
371     *       y is an ascii representation of an integer number of the max AP to remember per scan
372     *       z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
373     *           indicating entire ranges of channels
374     *       s is an ascii representation of an integer number of highest-strength AP
375     *           for which we'd like approximate distance reported
376     *
377     * The return value is an ascii integer representing a guess of the number of scans
378     * the firmware can remember before it runs out of buffer space or -1 on error
379     */
380    public String setBatchedScanSettings(BatchedScanSettings settings) {
381        if (settings == null) {
382            return doStringCommand("DRIVER WLS_BATCHING STOP");
383        }
384        String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec;
385        cmd += " MSCAN=" + settings.maxScansPerBatch;
386        if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
387            cmd += " BESTN=" + settings.maxApPerScan;
388        }
389        if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
390            cmd += " CHANNEL=<";
391            int i = 0;
392            for (String channel : settings.channelSet) {
393                cmd += (i > 0 ? "," : "") + channel;
394                ++i;
395            }
396            cmd += ">";
397        }
398        if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
399            cmd += " RTT=" + settings.maxApForDistance;
400        }
401        return doStringCommand(cmd);
402    }
403
404    public String getBatchedScanResults() {
405        return doStringCommand("DRIVER WLS_BATCHING GET");
406    }
407
408    public boolean startDriver() {
409        return doBooleanCommand("DRIVER START");
410    }
411
412    public boolean stopDriver() {
413        return doBooleanCommand("DRIVER STOP");
414    }
415
416
417    /**
418     * Start filtering out Multicast V4 packets
419     * @return {@code true} if the operation succeeded, {@code false} otherwise
420     *
421     * Multicast filtering rules work as follows:
422     *
423     * The driver can filter multicast (v4 and/or v6) and broadcast packets when in
424     * a power optimized mode (typically when screen goes off).
425     *
426     * In order to prevent the driver from filtering the multicast/broadcast packets, we have to
427     * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective
428     *
429     * DRIVER RXFILTER-ADD Num
430     *   where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6
431     *
432     * and DRIVER RXFILTER-START
433     * In order to stop the usage of these rules, we do
434     *
435     * DRIVER RXFILTER-STOP
436     * DRIVER RXFILTER-REMOVE Num
437     *   where Num is as described for RXFILTER-ADD
438     *
439     * The  SETSUSPENDOPT driver command overrides the filtering rules
440     */
441    public boolean startFilteringMulticastV4Packets() {
442        return doBooleanCommand("DRIVER RXFILTER-STOP")
443            && doBooleanCommand("DRIVER RXFILTER-REMOVE 2")
444            && doBooleanCommand("DRIVER RXFILTER-START");
445    }
446
447    /**
448     * Stop filtering out Multicast V4 packets.
449     * @return {@code true} if the operation succeeded, {@code false} otherwise
450     */
451    public boolean stopFilteringMulticastV4Packets() {
452        return doBooleanCommand("DRIVER RXFILTER-STOP")
453            && doBooleanCommand("DRIVER RXFILTER-ADD 2")
454            && doBooleanCommand("DRIVER RXFILTER-START");
455    }
456
457    /**
458     * Start filtering out Multicast V6 packets
459     * @return {@code true} if the operation succeeded, {@code false} otherwise
460     */
461    public boolean startFilteringMulticastV6Packets() {
462        return doBooleanCommand("DRIVER RXFILTER-STOP")
463            && doBooleanCommand("DRIVER RXFILTER-REMOVE 3")
464            && doBooleanCommand("DRIVER RXFILTER-START");
465    }
466
467    /**
468     * Stop filtering out Multicast V6 packets.
469     * @return {@code true} if the operation succeeded, {@code false} otherwise
470     */
471    public boolean stopFilteringMulticastV6Packets() {
472        return doBooleanCommand("DRIVER RXFILTER-STOP")
473            && doBooleanCommand("DRIVER RXFILTER-ADD 3")
474            && doBooleanCommand("DRIVER RXFILTER-START");
475    }
476
477    public int getBand() {
478       String ret = doStringCommand("DRIVER GETBAND");
479        if (!TextUtils.isEmpty(ret)) {
480            //reply is "BAND X" where X is the band
481            String[] tokens = ret.split(" ");
482            try {
483                if (tokens.length == 2) return Integer.parseInt(tokens[1]);
484            } catch (NumberFormatException e) {
485                return -1;
486            }
487        }
488        return -1;
489    }
490
491    public boolean setBand(int band) {
492        return doBooleanCommand("DRIVER SETBAND " + band);
493    }
494
495    /**
496      * Sets the bluetooth coexistence mode.
497      *
498      * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED},
499      *            {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or
500      *            {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}.
501      * @return Whether the mode was successfully set.
502      */
503    public boolean setBluetoothCoexistenceMode(int mode) {
504        return doBooleanCommand("DRIVER BTCOEXMODE " + mode);
505    }
506
507    /**
508     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
509     * some of the low-level scan parameters used by the driver are changed to
510     * reduce interference with A2DP streaming.
511     *
512     * @param isSet whether to enable or disable this mode
513     * @return {@code true} if the command succeeded, {@code false} otherwise.
514     */
515    public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) {
516        if (setCoexScanMode) {
517            return doBooleanCommand("DRIVER BTCOEXSCAN-START");
518        } else {
519            return doBooleanCommand("DRIVER BTCOEXSCAN-STOP");
520        }
521    }
522
523    public void enableSaveConfig() {
524        doBooleanCommand("SET update_config 1");
525    }
526
527    public boolean saveConfig() {
528        return doBooleanCommand("SAVE_CONFIG");
529    }
530
531    public boolean addToBlacklist(String bssid) {
532        if (TextUtils.isEmpty(bssid)) return false;
533        return doBooleanCommand("BLACKLIST " + bssid);
534    }
535
536    public boolean clearBlacklist() {
537        return doBooleanCommand("BLACKLIST clear");
538    }
539
540    public boolean setSuspendOptimizations(boolean enabled) {
541       // if (mSuspendOptEnabled == enabled) return true;
542        mSuspendOptEnabled = enabled;
543
544        Log.e("native", "do suspend " + enabled);
545        if (enabled) {
546            return doBooleanCommand("DRIVER SETSUSPENDMODE 1");
547        } else {
548            return doBooleanCommand("DRIVER SETSUSPENDMODE 0");
549        }
550    }
551
552    public boolean setCountryCode(String countryCode) {
553        return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
554    }
555
556    public void enableBackgroundScan(boolean enable) {
557        if (enable) {
558            doBooleanCommand("SET pno 1");
559        } else {
560            doBooleanCommand("SET pno 0");
561        }
562    }
563
564    public void enableAutoConnect(boolean enable) {
565        if (enable) {
566            doBooleanCommand("STA_AUTOCONNECT 1");
567        } else {
568            doBooleanCommand("STA_AUTOCONNECT 0");
569        }
570    }
571
572    public void setScanInterval(int scanInterval) {
573        doBooleanCommand("SCAN_INTERVAL " + scanInterval);
574    }
575
576    public void startTdls(String macAddr, boolean enable) {
577        if (enable) {
578            doBooleanCommand("TDLS_DISCOVER " + macAddr);
579            doBooleanCommand("TDLS_SETUP " + macAddr);
580        } else {
581            doBooleanCommand("TDLS_TEARDOWN " + macAddr);
582        }
583    }
584
585    /** Example output:
586     * RSSI=-65
587     * LINKSPEED=48
588     * NOISE=9999
589     * FREQUENCY=0
590     */
591    public String signalPoll() {
592        return doStringCommandWithoutLogging("SIGNAL_POLL");
593    }
594
595    /** Example outout:
596     * TXGOOD=396
597     * TXBAD=1
598     */
599    public String pktcntPoll() {
600        return doStringCommand("PKTCNT_POLL");
601    }
602
603    public void bssFlush() {
604        doBooleanCommand("BSS_FLUSH 0");
605    }
606
607    public boolean startWpsPbc(String bssid) {
608        if (TextUtils.isEmpty(bssid)) {
609            return doBooleanCommand("WPS_PBC");
610        } else {
611            return doBooleanCommand("WPS_PBC " + bssid);
612        }
613    }
614
615    public boolean startWpsPbc(String iface, String bssid) {
616        synchronized (mLock) {
617            if (TextUtils.isEmpty(bssid)) {
618                return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC");
619            } else {
620                return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC " + bssid);
621            }
622        }
623    }
624
625    public boolean startWpsPinKeypad(String pin) {
626        if (TextUtils.isEmpty(pin)) return false;
627        return doBooleanCommand("WPS_PIN any " + pin);
628    }
629
630    public boolean startWpsPinKeypad(String iface, String pin) {
631        if (TextUtils.isEmpty(pin)) return false;
632        synchronized (mLock) {
633            return doBooleanCommandNative("IFNAME=" + iface + " WPS_PIN any " + pin);
634        }
635    }
636
637
638    public String startWpsPinDisplay(String bssid) {
639        if (TextUtils.isEmpty(bssid)) {
640            return doStringCommand("WPS_PIN any");
641        } else {
642            return doStringCommand("WPS_PIN " + bssid);
643        }
644    }
645
646    public String startWpsPinDisplay(String iface, String bssid) {
647        synchronized (mLock) {
648            if (TextUtils.isEmpty(bssid)) {
649                return doStringCommandNative("IFNAME=" + iface + " WPS_PIN any");
650            } else {
651                return doStringCommandNative("IFNAME=" + iface + " WPS_PIN " + bssid);
652            }
653        }
654    }
655
656    /* Configures an access point connection */
657    public boolean startWpsRegistrar(String bssid, String pin) {
658        if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false;
659        return doBooleanCommand("WPS_REG " + bssid + " " + pin);
660    }
661
662    public boolean cancelWps() {
663        return doBooleanCommand("WPS_CANCEL");
664    }
665
666    public boolean setPersistentReconnect(boolean enabled) {
667        int value = (enabled == true) ? 1 : 0;
668        return doBooleanCommand("SET persistent_reconnect " + value);
669    }
670
671    public boolean setDeviceName(String name) {
672        return doBooleanCommand("SET device_name " + name);
673    }
674
675    public boolean setDeviceType(String type) {
676        return doBooleanCommand("SET device_type " + type);
677    }
678
679    public boolean setConfigMethods(String cfg) {
680        return doBooleanCommand("SET config_methods " + cfg);
681    }
682
683    public boolean setManufacturer(String value) {
684        return doBooleanCommand("SET manufacturer " + value);
685    }
686
687    public boolean setModelName(String value) {
688        return doBooleanCommand("SET model_name " + value);
689    }
690
691    public boolean setModelNumber(String value) {
692        return doBooleanCommand("SET model_number " + value);
693    }
694
695    public boolean setSerialNumber(String value) {
696        return doBooleanCommand("SET serial_number " + value);
697    }
698
699    public boolean setP2pSsidPostfix(String postfix) {
700        return doBooleanCommand("SET p2p_ssid_postfix " + postfix);
701    }
702
703    public boolean setP2pGroupIdle(String iface, int time) {
704        synchronized (mLock) {
705            return doBooleanCommandNative("IFNAME=" + iface + " SET p2p_group_idle " + time);
706        }
707    }
708
709    public void setPowerSave(boolean enabled) {
710        if (enabled) {
711            doBooleanCommand("SET ps 1");
712        } else {
713            doBooleanCommand("SET ps 0");
714        }
715    }
716
717    public boolean setP2pPowerSave(String iface, boolean enabled) {
718        synchronized (mLock) {
719            if (enabled) {
720                return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 1");
721            } else {
722                return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 0");
723            }
724        }
725    }
726
727    public boolean setWfdEnable(boolean enable) {
728        return doBooleanCommand("SET wifi_display " + (enable ? "1" : "0"));
729    }
730
731    public boolean setWfdDeviceInfo(String hex) {
732        return doBooleanCommand("WFD_SUBELEM_SET 0 " + hex);
733    }
734
735    /**
736     * "sta" prioritizes STA connection over P2P and "p2p" prioritizes
737     * P2P connection over STA
738     */
739    public boolean setConcurrencyPriority(String s) {
740        return doBooleanCommand("P2P_SET conc_pref " + s);
741    }
742
743    public boolean p2pFind() {
744        return doBooleanCommand("P2P_FIND");
745    }
746
747    public boolean p2pFind(int timeout) {
748        if (timeout <= 0) {
749            return p2pFind();
750        }
751        return doBooleanCommand("P2P_FIND " + timeout);
752    }
753
754    public boolean p2pStopFind() {
755       return doBooleanCommand("P2P_STOP_FIND");
756    }
757
758    public boolean p2pListen() {
759        return doBooleanCommand("P2P_LISTEN");
760    }
761
762    public boolean p2pListen(int timeout) {
763        if (timeout <= 0) {
764            return p2pListen();
765        }
766        return doBooleanCommand("P2P_LISTEN " + timeout);
767    }
768
769    public boolean p2pExtListen(boolean enable, int period, int interval) {
770        if (enable && interval < period) {
771            return false;
772        }
773        return doBooleanCommand("P2P_EXT_LISTEN"
774                    + (enable ? (" " + period + " " + interval) : ""));
775    }
776
777    public boolean p2pSetChannel(int lc, int oc) {
778        if (DBG) Log.d(mTAG, "p2pSetChannel: lc="+lc+", oc="+oc);
779
780        if (lc >=1 && lc <= 11) {
781            if (!doBooleanCommand("P2P_SET listen_channel " + lc)) {
782                return false;
783            }
784        } else if (lc != 0) {
785            return false;
786        }
787
788        if (oc >= 1 && oc <= 165 ) {
789            int freq = (oc <= 14 ? 2407 : 5000) + oc * 5;
790            return doBooleanCommand("P2P_SET disallow_freq 1000-"
791                    + (freq - 5) + "," + (freq + 5) + "-6000");
792        } else if (oc == 0) {
793            /* oc==0 disables "P2P_SET disallow_freq" (enables all freqs) */
794            return doBooleanCommand("P2P_SET disallow_freq \"\"");
795        }
796
797        return false;
798    }
799
800    public boolean p2pFlush() {
801        return doBooleanCommand("P2P_FLUSH");
802    }
803
804    /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad]
805        [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */
806    public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
807        if (config == null) return null;
808        List<String> args = new ArrayList<String>();
809        WpsInfo wps = config.wps;
810        args.add(config.deviceAddress);
811
812        switch (wps.setup) {
813            case WpsInfo.PBC:
814                args.add("pbc");
815                break;
816            case WpsInfo.DISPLAY:
817                if (TextUtils.isEmpty(wps.pin)) {
818                    args.add("pin");
819                } else {
820                    args.add(wps.pin);
821                }
822                args.add("display");
823                break;
824            case WpsInfo.KEYPAD:
825                args.add(wps.pin);
826                args.add("keypad");
827                break;
828            case WpsInfo.LABEL:
829                args.add(wps.pin);
830                args.add("label");
831            default:
832                break;
833        }
834
835        if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) {
836            args.add("persistent");
837        }
838
839        if (joinExistingGroup) {
840            args.add("join");
841        } else {
842            //TODO: This can be adapted based on device plugged in state and
843            //device battery state
844            int groupOwnerIntent = config.groupOwnerIntent;
845            if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
846                groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
847            }
848            args.add("go_intent=" + groupOwnerIntent);
849        }
850
851        String command = "P2P_CONNECT ";
852        for (String s : args) command += s + " ";
853
854        return doStringCommand(command);
855    }
856
857    public boolean p2pCancelConnect() {
858        return doBooleanCommand("P2P_CANCEL");
859    }
860
861    public boolean p2pProvisionDiscovery(WifiP2pConfig config) {
862        if (config == null) return false;
863
864        switch (config.wps.setup) {
865            case WpsInfo.PBC:
866                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " pbc");
867            case WpsInfo.DISPLAY:
868                //We are doing display, so provision discovery is keypad
869                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " keypad");
870            case WpsInfo.KEYPAD:
871                //We are doing keypad, so provision discovery is display
872                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " display");
873            default:
874                break;
875        }
876        return false;
877    }
878
879    public boolean p2pGroupAdd(boolean persistent) {
880        if (persistent) {
881            return doBooleanCommand("P2P_GROUP_ADD persistent");
882        }
883        return doBooleanCommand("P2P_GROUP_ADD");
884    }
885
886    public boolean p2pGroupAdd(int netId) {
887        return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId);
888    }
889
890    public boolean p2pGroupRemove(String iface) {
891        if (TextUtils.isEmpty(iface)) return false;
892        synchronized (mLock) {
893            return doBooleanCommandNative("IFNAME=" + iface + " P2P_GROUP_REMOVE " + iface);
894        }
895    }
896
897    public boolean p2pReject(String deviceAddress) {
898        return doBooleanCommand("P2P_REJECT " + deviceAddress);
899    }
900
901    /* Invite a peer to a group */
902    public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
903        if (TextUtils.isEmpty(deviceAddress)) return false;
904
905        if (group == null) {
906            return doBooleanCommand("P2P_INVITE peer=" + deviceAddress);
907        } else {
908            return doBooleanCommand("P2P_INVITE group=" + group.getInterface()
909                    + " peer=" + deviceAddress + " go_dev_addr=" + group.getOwner().deviceAddress);
910        }
911    }
912
913    /* Reinvoke a persistent connection */
914    public boolean p2pReinvoke(int netId, String deviceAddress) {
915        if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false;
916
917        return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
918    }
919
920    public String p2pGetSsid(String deviceAddress) {
921        return p2pGetParam(deviceAddress, "oper_ssid");
922    }
923
924    public String p2pGetDeviceAddress() {
925        String status = status();
926        if (status == null) return "";
927
928        String[] tokens = status.split("\n");
929        for (String token : tokens) {
930            if (token.startsWith("p2p_device_address=")) {
931                String[] nameValue = token.split("=");
932                if (nameValue.length != 2) break;
933                return nameValue[1];
934            }
935        }
936        return "";
937    }
938
939    public int getGroupCapability(String deviceAddress) {
940        int gc = 0;
941        if (TextUtils.isEmpty(deviceAddress)) return gc;
942        String peerInfo = p2pPeer(deviceAddress);
943        if (TextUtils.isEmpty(peerInfo)) return gc;
944
945        String[] tokens = peerInfo.split("\n");
946        for (String token : tokens) {
947            if (token.startsWith("group_capab=")) {
948                String[] nameValue = token.split("=");
949                if (nameValue.length != 2) break;
950                try {
951                    return Integer.decode(nameValue[1]);
952                } catch(NumberFormatException e) {
953                    return gc;
954                }
955            }
956        }
957        return gc;
958    }
959
960    public String p2pPeer(String deviceAddress) {
961        return doStringCommand("P2P_PEER " + deviceAddress);
962    }
963
964    private String p2pGetParam(String deviceAddress, String key) {
965        if (deviceAddress == null) return null;
966
967        String peerInfo = p2pPeer(deviceAddress);
968        if (peerInfo == null) return null;
969        String[] tokens= peerInfo.split("\n");
970
971        key += "=";
972        for (String token : tokens) {
973            if (token.startsWith(key)) {
974                String[] nameValue = token.split("=");
975                if (nameValue.length != 2) break;
976                return nameValue[1];
977            }
978        }
979        return null;
980    }
981
982    public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
983        /*
984         * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
985         * P2P_SERVICE_ADD upnp <version hex> <service>
986         *
987         * e.g)
988         * [Bonjour]
989         * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
990         * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
991         * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
992         * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
993         *  09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
994         *
995         * [UPnP]
996         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
997         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
998         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
999         * -org:device:InternetGatewayDevice:1
1000         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
1001         * -org:service:ContentDirectory:2
1002         */
1003        for (String s : servInfo.getSupplicantQueryList()) {
1004            String command = "P2P_SERVICE_ADD";
1005            command += (" " + s);
1006            if (!doBooleanCommand(command)) {
1007                return false;
1008            }
1009        }
1010        return true;
1011    }
1012
1013    public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
1014        /*
1015         * P2P_SERVICE_DEL bonjour <query hexdump>
1016         * P2P_SERVICE_DEL upnp <version hex> <service>
1017         */
1018        for (String s : servInfo.getSupplicantQueryList()) {
1019            String command = "P2P_SERVICE_DEL ";
1020
1021            String[] data = s.split(" ");
1022            if (data.length < 2) {
1023                return false;
1024            }
1025            if ("upnp".equals(data[0])) {
1026                command += s;
1027            } else if ("bonjour".equals(data[0])) {
1028                command += data[0];
1029                command += (" " + data[1]);
1030            } else {
1031                return false;
1032            }
1033            if (!doBooleanCommand(command)) {
1034                return false;
1035            }
1036        }
1037        return true;
1038    }
1039
1040    public boolean p2pServiceFlush() {
1041        return doBooleanCommand("P2P_SERVICE_FLUSH");
1042    }
1043
1044    public String p2pServDiscReq(String addr, String query) {
1045        String command = "P2P_SERV_DISC_REQ";
1046        command += (" " + addr);
1047        command += (" " + query);
1048
1049        return doStringCommand(command);
1050    }
1051
1052    public boolean p2pServDiscCancelReq(String id) {
1053        return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
1054    }
1055
1056    /* Set the current mode of miracast operation.
1057     *  0 = disabled
1058     *  1 = operating as source
1059     *  2 = operating as sink
1060     */
1061    public void setMiracastMode(int mode) {
1062        // Note: optional feature on the driver. It is ok for this to fail.
1063        doBooleanCommand("DRIVER MIRACAST " + mode);
1064    }
1065
1066    public String getNfcWpsConfigurationToken(int netId) {
1067        return doStringCommand("WPS_NFC_CONFIG_TOKEN WPS " + netId);
1068    }
1069
1070    public boolean fetchAnqp(String bssid, String subtypes) {
1071        return doBooleanCommand("ANQP_GET " + bssid + " " + subtypes);
1072    }
1073
1074
1075    /* WIFI HAL support */
1076
1077    private static final String TAG = "WifiNative-HAL";
1078    private static long sWifiHalHandle = 0;  /* used by JNI to save wifi_handle */
1079    private static long[] sWifiIfaceHandles = null;  /* used by JNI to save interface handles */
1080    private static int sWlan0Index = -1;
1081    private static int sP2p0Index = -1;
1082
1083    private static boolean sHalIsStarted = false;
1084
1085    private static native boolean startHalNative();
1086    private static native void stopHalNative();
1087    private static native void waitForHalEventNative();
1088
1089    private static class MonitorThread extends Thread {
1090        public void run() {
1091            Log.i(TAG, "Waiting for HAL events mWifiHalHandle=" + Long.toString(sWifiHalHandle));
1092            waitForHalEventNative();
1093        }
1094    }
1095
1096    synchronized public static boolean startHal() {
1097        Log.i(TAG, "startHal");
1098        synchronized (mLock) {
1099            if (sHalIsStarted)
1100                return true;
1101            if (startHalNative()) {
1102                new MonitorThread().start();
1103                sHalIsStarted = true;
1104                return true;
1105            } else {
1106                Log.i(TAG, "Could not start hal");
1107                sHalIsStarted = false;
1108                return false;
1109            }
1110        }
1111    }
1112
1113    synchronized public static void stopHal() {
1114        stopHalNative();
1115    }
1116
1117    private static native int getInterfacesNative();
1118
1119    synchronized public static int getInterfaces() {
1120        synchronized (mLock) {
1121            int num = getInterfacesNative();
1122            for (int i = 0; i < num; i++) {
1123                String name = getInterfaceNameNative(i);
1124                Log.i(TAG, "interface[" + i + "] = " + name);
1125                if (name.equals("wlan0")) {
1126                    sWlan0Index = i;
1127                } else if (name.equals("p2p0")) {
1128                    sP2p0Index = i;
1129                }
1130            }
1131            return num;
1132        }
1133    }
1134
1135    private static native String getInterfaceNameNative(int index);
1136
1137    public static void printInterfaceNames() {
1138        synchronized (mLock) {
1139            for (int i = 0; i < sWifiIfaceHandles.length; i++) {
1140                String name = getInterfaceNameNative(i);
1141                Log.i(TAG, "interface[" + i + "] = " + name);
1142            }
1143        }
1144    }
1145
1146    public static class ScanCapabilities {
1147        public int  max_scan_cache_size;                 // in number of scan results??
1148        public int  max_scan_buckets;
1149        public int  max_ap_cache_per_scan;
1150        public int  max_rssi_sample_size;
1151        public int  max_scan_reporting_threshold;        // in number of scan results??
1152        public int  max_hotlist_aps;
1153        public int  max_significant_wifi_change_aps;
1154    }
1155
1156    public static boolean getScanCapabilities(ScanCapabilities capabilities) {
1157        return getScanCapabilitiesNative(sWlan0Index, capabilities);
1158    }
1159
1160    private static native boolean getScanCapabilitiesNative(
1161            int iface, ScanCapabilities capabilities);
1162
1163    private static native boolean startScanNative(int iface, int id, ScanSettings settings);
1164    private static native boolean stopScanNative(int iface, int id);
1165    private static native ScanResult[] getScanResultsNative(int iface, boolean flush);
1166    private static native WifiLinkLayerStats getWifiLinkLayerStatsNative(int iface);
1167
1168    public static class ChannelSettings {
1169        int frequency;
1170        int dwell_time_ms;
1171        boolean passive;
1172    }
1173
1174    public static class BucketSettings {
1175        int bucket;
1176        int band;
1177        int period_ms;
1178        int report_events;
1179        int num_channels;
1180        ChannelSettings channels[] = new ChannelSettings[8];
1181    }
1182
1183    public static class ScanSettings {
1184        int base_period_ms;
1185        int max_ap_per_scan;
1186        int report_threshold;
1187        int num_buckets;
1188        BucketSettings buckets[] = new BucketSettings[8];
1189    }
1190
1191    public static interface ScanEventHandler {
1192        void onScanResultsAvailable();
1193        void onFullScanResult(ScanResult result, WifiScanner.InformationElement elems[]);
1194        void onScanPaused();
1195        void onScanRestarted();
1196    }
1197
1198    synchronized static void onScanResultsAvailable(int id) {
1199        sScanEventHandler.onScanResultsAvailable();
1200    }
1201
1202    synchronized static void onFullScanResult(int id, ScanResult result, byte bytes[]) {
1203        Log.i(TAG, "Got a full scan results event, ssid = " + result.SSID + ", " +
1204                "num = " + bytes.length);
1205
1206        int num = 0;
1207        for (int i = 0; i < bytes.length; ) {
1208            num++;
1209            int type  = (int) bytes[i] & 0xFF;
1210            int len = (int) bytes[i + 1] & 0xFF;
1211            if (len < 0) {
1212                Log.e(TAG, "bad length; returning");
1213                return;
1214            }
1215            i += len + 2;
1216            Log.i(TAG, "bytes[" + i + "] = [" + type + ", " + len + "]" + ", next = " + i);
1217        }
1218
1219        WifiScanner.InformationElement elements[] = new WifiScanner.InformationElement[num];
1220        for (int i = 0, index = 0; i < num; i++) {
1221            int type  = (int) bytes[index] & 0xFF;
1222            int len = (int) bytes[index + 1] & 0xFF;
1223            Log.i(TAG, "index = " + index + ", type = " + type + ", len = " + len);
1224            WifiScanner.InformationElement elem = new WifiScanner.InformationElement();
1225            elem.id = type;
1226            elem.bytes = new byte[len];
1227            for (int j = 0; j < len; j++) {
1228                elem.bytes[j] = bytes[index + j + 2];
1229            }
1230            elements[i] = elem;
1231            index += (len + 2);
1232        }
1233
1234        sScanEventHandler.onFullScanResult(result, elements);
1235    }
1236
1237    private static int sScanCmdId = 0;
1238    private static ScanEventHandler sScanEventHandler;
1239    private static ScanSettings sScanSettings;
1240
1241    synchronized public static boolean startScan(
1242            ScanSettings settings, ScanEventHandler eventHandler) {
1243        synchronized (mLock) {
1244
1245            if (sScanCmdId != 0) {
1246                stopScan();
1247            } else if (sScanSettings != null || sScanEventHandler != null) {
1248                /* current scan is paused; no need to stop it */
1249            }
1250
1251            sScanCmdId = getNewCmdIdLocked();
1252
1253            sScanSettings = settings;
1254            sScanEventHandler = eventHandler;
1255
1256            if (startScanNative(sWlan0Index, sScanCmdId, settings) == false) {
1257                sScanEventHandler = null;
1258                sScanSettings = null;
1259                return false;
1260            }
1261
1262            return true;
1263        }
1264    }
1265
1266    synchronized public static void stopScan() {
1267        synchronized (mLock) {
1268            stopScanNative(sWlan0Index, sScanCmdId);
1269            sScanSettings = null;
1270            sScanEventHandler = null;
1271            sScanCmdId = 0;
1272        }
1273    }
1274
1275    synchronized public static void pauseScan() {
1276        synchronized (mLock) {
1277            if (sScanCmdId != 0 && sScanSettings != null && sScanEventHandler != null) {
1278                Log.d(TAG, "Pausing scan");
1279                stopScanNative(sWlan0Index, sScanCmdId);
1280                sScanCmdId = 0;
1281                sScanEventHandler.onScanPaused();
1282            }
1283        }
1284    }
1285
1286    synchronized public static void restartScan() {
1287        synchronized (mLock) {
1288            if (sScanCmdId == 0 && sScanSettings != null && sScanEventHandler != null) {
1289                Log.d(TAG, "Restarting scan");
1290                startScan(sScanSettings, sScanEventHandler);
1291                sScanEventHandler.onScanRestarted();
1292            }
1293        }
1294    }
1295
1296    synchronized public static ScanResult[] getScanResults() {
1297        synchronized (mLock) {
1298            return getScanResultsNative(sWlan0Index, /* flush = */ false);
1299        }
1300    }
1301
1302    public static interface HotlistEventHandler {
1303        void onHotlistApFound (ScanResult[]result);
1304    }
1305
1306    private static int sHotlistCmdId = 0;
1307    private static HotlistEventHandler sHotlistEventHandler;
1308
1309    private native static boolean setHotlistNative(int iface, int id,
1310            WifiScanner.HotlistSettings settings);
1311    private native static boolean resetHotlistNative(int iface, int id);
1312
1313    synchronized public static boolean setHotlist(WifiScanner.HotlistSettings settings,
1314                                    HotlistEventHandler eventHandler) {
1315        synchronized (mLock) {
1316            if (sHotlistCmdId != 0) {
1317                return false;
1318            } else {
1319                sHotlistCmdId = getNewCmdIdLocked();
1320            }
1321
1322            sHotlistEventHandler = eventHandler;
1323            if (setHotlistNative(sWlan0Index, sScanCmdId, settings) == false) {
1324                sHotlistEventHandler = null;
1325                return false;
1326            }
1327
1328            return true;
1329        }
1330    }
1331
1332    synchronized public static void resetHotlist() {
1333        synchronized (mLock) {
1334            if (sHotlistCmdId != 0) {
1335                resetHotlistNative(sWlan0Index, sHotlistCmdId);
1336                sHotlistCmdId = 0;
1337                sHotlistEventHandler = null;
1338            }
1339        }
1340    }
1341
1342    synchronized public static void onHotlistApFound(int id, ScanResult[] results) {
1343        synchronized (mLock) {
1344            sHotlistEventHandler.onHotlistApFound(results);
1345        }
1346    }
1347
1348    public static interface SignificantWifiChangeEventHandler {
1349        void onChangesFound(ScanResult[] result);
1350    }
1351
1352    private static SignificantWifiChangeEventHandler sSignificantWifiChangeHandler;
1353    private static int sSignificantWifiChangeCmdId;
1354
1355    private static native boolean trackSignificantWifiChangeNative(
1356            int iface, int id, WifiScanner.WifiChangeSettings settings);
1357    private static native boolean untrackSignificantWifiChangeNative(int iface, int id);
1358
1359    synchronized public static boolean trackSignificantWifiChange(
1360            WifiScanner.WifiChangeSettings settings, SignificantWifiChangeEventHandler handler) {
1361        synchronized (mLock) {
1362            if (sSignificantWifiChangeCmdId != 0) {
1363                return false;
1364            } else {
1365                sSignificantWifiChangeCmdId = getNewCmdIdLocked();
1366            }
1367
1368            sSignificantWifiChangeHandler = handler;
1369            if (trackSignificantWifiChangeNative(sWlan0Index, sScanCmdId, settings) == false) {
1370                sHotlistEventHandler = null;
1371                return false;
1372            }
1373
1374            return true;
1375        }
1376    }
1377
1378    synchronized static void untrackSignificantWifiChange() {
1379        synchronized (mLock) {
1380            if (sSignificantWifiChangeCmdId != 0) {
1381                untrackSignificantWifiChangeNative(sWlan0Index, sSignificantWifiChangeCmdId);
1382                sSignificantWifiChangeCmdId = 0;
1383                sSignificantWifiChangeHandler = null;
1384            }
1385        }
1386    }
1387
1388    synchronized static void onSignificantWifiChange(int id, ScanResult[] results) {
1389        synchronized (mLock) {
1390            sSignificantWifiChangeHandler.onChangesFound(results);
1391        }
1392    }
1393
1394    synchronized public static WifiLinkLayerStats getWifiLinkLayerStats() {
1395        synchronized (mLock) {
1396            if (!sHalIsStarted)
1397                startHal();
1398            if (sHalIsStarted)
1399                return getWifiLinkLayerStatsNative(sWlan0Index);
1400        }
1401        return null;
1402    }
1403}
1404