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