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