1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.scanner;
18 
19 import android.app.AlarmManager;
20 import android.content.Context;
21 import android.net.wifi.ScanResult;
22 import android.net.wifi.WifiScanner;
23 import android.net.wifi.WifiScanner.WifiBandIndex;
24 import android.net.wifi.util.ScanResultUtil;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.util.Log;
29 
30 import com.android.server.wifi.Clock;
31 import com.android.server.wifi.ScanDetail;
32 import com.android.server.wifi.WifiGlobals;
33 import com.android.server.wifi.WifiMonitor;
34 import com.android.server.wifi.WifiNative;
35 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
36 import com.android.server.wifi.util.NativeUtil;
37 
38 import java.io.FileDescriptor;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 
46 import javax.annotation.concurrent.GuardedBy;
47 
48 /**
49  * Implementation of the WifiScanner HAL API that uses wificond to perform all scans
50  * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
51  */
52 public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback {
53     private static final String TAG = "WificondScannerImpl";
54     private static final boolean DBG = false;
55 
56     public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
57     // Default number of networks that can be specified to wificond per scan request
58     public static final int DEFAULT_NUM_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
59 
60     private static final int SCAN_BUFFER_CAPACITY = 10;
61     private static final int MAX_APS_PER_SCAN = 32;
62     private static final int MAX_SCAN_BUCKETS = 16;
63 
64     private final Context mContext;
65     private final WifiGlobals mWifiGlobals;
66     private final WifiNative mWifiNative;
67     private final WifiMonitor mWifiMonitor;
68     private final AlarmManager mAlarmManager;
69     private final Handler mEventHandler;
70     private final ChannelHelper mChannelHelper;
71     private final Clock mClock;
72 
73     private final Object mSettingsLock = new Object();
74 
75     private ArrayList<ScanDetail> mNativeScanResults;
76     private ArrayList<ScanDetail> mNativePnoScanResults;
77     private WifiScanner.ScanData mLatestSingleScanResult =
78             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
79     private int mMaxNumScanSsids = -1;
80     private int mNextHiddenNetworkScanId = 0;
81 
82     // Settings for the currently running single scan, null if no scan active
83     private LastScanSettings mLastScanSettings = null;
84     // Settings for the currently running pno scan, null if no scan active
85     private LastPnoScanSettings mLastPnoScanSettings = null;
86 
87     /**
88      * Duration to wait before timing out a scan.
89      *
90      * The expected behavior is that the hardware will return a failed scan if it does not
91      * complete, but timeout just in case it does not.
92      */
93     private static final long SCAN_TIMEOUT_MS = 15000;
94 
95     @GuardedBy("mSettingsLock")
96     private AlarmManager.OnAlarmListener mScanTimeoutListener;
97 
WificondScannerImpl(Context context, String ifaceName, WifiGlobals wifiGlobals, WifiNative wifiNative, WifiMonitor wifiMonitor, ChannelHelper channelHelper, Looper looper, Clock clock)98     public WificondScannerImpl(Context context, String ifaceName, WifiGlobals wifiGlobals,
99                                WifiNative wifiNative, WifiMonitor wifiMonitor,
100                                ChannelHelper channelHelper, Looper looper, Clock clock) {
101         super(ifaceName);
102         mContext = context;
103         mWifiGlobals = wifiGlobals;
104         mWifiNative = wifiNative;
105         mWifiMonitor = wifiMonitor;
106         mChannelHelper = channelHelper;
107         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
108         mEventHandler = new Handler(looper, this);
109         mClock = clock;
110 
111         wifiMonitor.registerHandler(getIfaceName(),
112                 WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
113         wifiMonitor.registerHandler(getIfaceName(),
114                 WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
115         wifiMonitor.registerHandler(getIfaceName(),
116                 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
117     }
118 
119     @Override
cleanup()120     public void cleanup() {
121         synchronized (mSettingsLock) {
122             cancelScanTimeout();
123             reportScanFailure(WifiScanner.REASON_UNSPECIFIED);
124             stopHwPnoScan();
125             mMaxNumScanSsids = -1;
126             mNextHiddenNetworkScanId = 0;
127             mLastScanSettings = null; // finally clear any active scan
128             mLastPnoScanSettings = null; // finally clear any active scan
129             mWifiMonitor.deregisterHandler(getIfaceName(),
130                     WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
131             mWifiMonitor.deregisterHandler(getIfaceName(),
132                     WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
133             mWifiMonitor.deregisterHandler(getIfaceName(),
134                     WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
135         }
136     }
137 
138     @Override
getScanCapabilities(WifiNative.ScanCapabilities capabilities)139     public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) {
140         capabilities.max_scan_cache_size = Integer.MAX_VALUE;
141         capabilities.max_scan_buckets = MAX_SCAN_BUCKETS;
142         capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN;
143         capabilities.max_rssi_sample_size = 8;
144         capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY;
145         return true;
146     }
147 
148     @Override
getChannelHelper()149     public ChannelHelper getChannelHelper() {
150         return mChannelHelper;
151     }
152 
153     @Override
startSingleScan(WifiNative.ScanSettings settings, WifiNative.ScanEventHandler eventHandler)154     public boolean startSingleScan(WifiNative.ScanSettings settings,
155             WifiNative.ScanEventHandler eventHandler) {
156         if (eventHandler == null || settings == null) {
157             Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
158                     + ",eventHandler=" + eventHandler);
159             return false;
160         }
161         synchronized (mSettingsLock) {
162             if (mLastScanSettings != null) {
163                 Log.w(TAG, "A single scan is already running");
164                 return false;
165             }
166 
167             ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
168             boolean reportFullResults = false;
169 
170             for (int i = 0; i < settings.num_buckets; ++i) {
171                 WifiNative.BucketSettings bucketSettings = settings.buckets[i];
172                 if ((bucketSettings.report_events
173                                 & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
174                     reportFullResults = true;
175                 }
176                 allFreqs.addChannels(bucketSettings);
177             }
178 
179             List<String> hiddenNetworkSSIDSet = new ArrayList<>();
180             if (settings.hiddenNetworks != null) {
181                 boolean executeRoundRobin = true;
182                 int maxNumScanSsids = mMaxNumScanSsids;
183                 if (maxNumScanSsids <= 0) {
184                     // Subtract 1 to account for the wildcard/broadcast probe request that
185                     // wificond adds to the scan set.
186                     mMaxNumScanSsids = mWifiNative.getMaxSsidsPerScan(getIfaceName()) - 1;
187                     if (mMaxNumScanSsids > 0) {
188                         maxNumScanSsids = mMaxNumScanSsids;
189                     } else {
190                         maxNumScanSsids = DEFAULT_NUM_HIDDEN_NETWORK_IDS_PER_SCAN;
191                         executeRoundRobin = false;
192                     }
193                 }
194                 int numHiddenNetworksPerScan =
195                         Math.min(settings.hiddenNetworks.length, maxNumScanSsids);
196                 if (numHiddenNetworksPerScan == settings.hiddenNetworks.length
197                         || mNextHiddenNetworkScanId >= settings.hiddenNetworks.length
198                         || !executeRoundRobin) {
199                     mNextHiddenNetworkScanId = 0;
200                 }
201                 if (DBG) {
202                     Log.d(TAG, "Scanning for " + numHiddenNetworksPerScan + " out of "
203                             + settings.hiddenNetworks.length + " total hidden networks");
204                     Log.d(TAG, "Scan hidden networks starting at id=" + mNextHiddenNetworkScanId);
205                 }
206 
207                 int id = mNextHiddenNetworkScanId;
208                 for (int i = 0; i < numHiddenNetworksPerScan; i++, id++) {
209                     hiddenNetworkSSIDSet.add(
210                             settings.hiddenNetworks[id % settings.hiddenNetworks.length].ssid);
211                 }
212                 mNextHiddenNetworkScanId = id % settings.hiddenNetworks.length;
213             }
214             mLastScanSettings = new LastScanSettings(
215                     mClock.getElapsedSinceBootNanos(),
216                     reportFullResults, allFreqs, eventHandler);
217 
218             int scanStatus = WifiScanner.REASON_UNSPECIFIED;
219             Set<Integer> freqs = Collections.emptySet();
220             if (!allFreqs.isEmpty()) {
221                 freqs = allFreqs.getScanFreqs();
222                 scanStatus = mWifiNative.scan(
223                         getIfaceName(), settings.scanType, freqs, hiddenNetworkSSIDSet,
224                         settings.enable6GhzRnr, settings.vendorIes);
225                 if (scanStatus != WifiScanner.REASON_SUCCEEDED) {
226                     Log.e(TAG, "Failed to start scan, freqs=" + freqs + " status: "
227                             + scanStatus);
228                 }
229             } else {
230                 // There is a scan request but no available channels could be scanned for.
231                 // We regard it as a scan failure in this case.
232                 Log.e(TAG, "Failed to start scan because there is no available channel to scan");
233             }
234             if (scanStatus == WifiScanner.REASON_SUCCEEDED) {
235                 if (DBG) {
236                     Log.d(TAG, "Starting wifi scan for freqs=" + freqs
237                             + " on iface " + getIfaceName());
238                 }
239 
240                 mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
241                     @Override public void onAlarm() {
242                         handleScanTimeout();
243                     }
244                 };
245 
246                 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
247                         mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
248                         TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
249             } else {
250                 // indicate scan failure async
251                 int finalScanStatus = scanStatus;
252                 mEventHandler.post(() -> reportScanFailure(finalScanStatus));
253             }
254 
255             return true;
256         }
257     }
258 
259     @Override
getLatestSingleScanResults()260     public WifiScanner.ScanData getLatestSingleScanResults() {
261         return mLatestSingleScanResult;
262     }
263 
264     @Override
startBatchedScan(WifiNative.ScanSettings settings, WifiNative.ScanEventHandler eventHandler)265     public boolean startBatchedScan(WifiNative.ScanSettings settings,
266             WifiNative.ScanEventHandler eventHandler) {
267         Log.w(TAG, "startBatchedScan() is not supported");
268         return false;
269     }
270 
271     @Override
stopBatchedScan()272     public void stopBatchedScan() {
273         Log.w(TAG, "stopBatchedScan() is not supported");
274     }
275 
276     @Override
pauseBatchedScan()277     public void pauseBatchedScan() {
278         Log.w(TAG, "pauseBatchedScan() is not supported");
279     }
280 
281     @Override
restartBatchedScan()282     public void restartBatchedScan() {
283         Log.w(TAG, "restartBatchedScan() is not supported");
284     }
285 
handleScanTimeout()286     private void handleScanTimeout() {
287         synchronized (mSettingsLock) {
288             Log.e(TAG, "Timed out waiting for scan result from wificond");
289             reportScanFailure(WifiScanner.REASON_TIMEOUT);
290             mScanTimeoutListener = null;
291         }
292     }
293 
294     @Override
handleMessage(Message msg)295     public boolean handleMessage(Message msg) {
296         switch(msg.what) {
297             case WifiMonitor.SCAN_FAILED_EVENT:
298                 Log.w(TAG, "Scan failed: error code: " + msg.arg1);
299                 cancelScanTimeout();
300                 reportScanFailure(msg.arg1);
301                 break;
302             case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
303                 pollLatestScanDataForPno();
304                 break;
305             case WifiMonitor.SCAN_RESULTS_EVENT:
306                 cancelScanTimeout();
307                 pollLatestScanData();
308                 break;
309             default:
310                 // ignore unknown event
311         }
312         return true;
313     }
314 
cancelScanTimeout()315     private void cancelScanTimeout() {
316         synchronized (mSettingsLock) {
317             if (mScanTimeoutListener != null) {
318                 mAlarmManager.cancel(mScanTimeoutListener);
319                 mScanTimeoutListener = null;
320             }
321         }
322     }
323 
reportScanFailure(int errorCode)324     private void reportScanFailure(int errorCode) {
325         synchronized (mSettingsLock) {
326             if (mLastScanSettings != null) {
327                 if (mLastScanSettings.singleScanEventHandler != null) {
328                     mLastScanSettings.singleScanEventHandler
329                             .onScanRequestFailed(errorCode);
330                 }
331                 mLastScanSettings = null;
332             }
333         }
334     }
335 
reportPnoScanFailure()336     private void reportPnoScanFailure() {
337         synchronized (mSettingsLock) {
338             if (mLastPnoScanSettings != null) {
339                 if (mLastPnoScanSettings.pnoScanEventHandler != null) {
340                     mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed();
341                 }
342                 // Clean up PNO state, we don't want to continue PNO scanning.
343                 mLastPnoScanSettings = null;
344             }
345         }
346     }
347 
pollLatestScanDataForPno()348     private void pollLatestScanDataForPno() {
349         synchronized (mSettingsLock) {
350             if (mLastPnoScanSettings == null) {
351                  // got a scan before we started scanning or after scan was canceled
352                 return;
353             }
354             mNativePnoScanResults = mWifiNative.getPnoScanResults(getIfaceName());
355             List<ScanResult> hwPnoScanResults = new ArrayList<>();
356             int numFilteredScanResults = 0;
357             for (int i = 0; i < mNativePnoScanResults.size(); ++i) {
358                 ScanResult result = mNativePnoScanResults.get(i).getScanResult();
359                 // nanoseconds -> microseconds
360                 if (result.timestamp >= mLastPnoScanSettings.startTimeNanos / 1_000) {
361                     hwPnoScanResults.add(result);
362                 } else {
363                     numFilteredScanResults++;
364                 }
365             }
366 
367             if (numFilteredScanResults != 0) {
368                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
369             }
370 
371             if (mLastPnoScanSettings.pnoScanEventHandler != null) {
372                 ScanResult[] pnoScanResultsArray =
373                         hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]);
374                 mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
375             }
376         }
377     }
378 
379     /**
380      * Return one of the WIFI_BAND_# values that was scanned for in this scan.
381      */
getScannedBandsInternal(ChannelCollection channelCollection)382     private static int getScannedBandsInternal(ChannelCollection channelCollection) {
383         int bandsScanned = WifiScanner.WIFI_BAND_UNSPECIFIED;
384 
385         for (@WifiBandIndex int i = 0; i < WifiScanner.WIFI_BAND_COUNT; i++) {
386             if (channelCollection.containsBand(1 << i)) {
387                 bandsScanned |= 1 << i;
388             }
389         }
390         return bandsScanned;
391     }
392 
pollLatestScanData()393     private void pollLatestScanData() {
394         synchronized (mSettingsLock) {
395             if (mLastScanSettings == null) {
396                  // got a scan before we started scanning or after scan was canceled
397                 return;
398             }
399 
400             mNativeScanResults = mWifiNative.getScanResults(getIfaceName());
401             List<ScanResult> singleScanResults = new ArrayList<>();
402             int numFilteredScanResults = 0;
403             for (int i = 0; i < mNativeScanResults.size(); ++i) {
404                 ScanResult result = mNativeScanResults.get(i).getScanResult();
405                 // nanoseconds -> microseconds
406                 if (result.timestamp >= mLastScanSettings.startTimeNanos / 1_000) {
407                     // Allow even not explicitly requested 6Ghz results because they could be found
408                     // via 6Ghz RNR.
409                     if (mLastScanSettings.singleScanFreqs.containsChannel(
410                                     result.frequency) || ScanResult.is6GHz(result.frequency)) {
411                         singleScanResults.add(result);
412                     } else {
413                         numFilteredScanResults++;
414                     }
415                 } else {
416                     numFilteredScanResults++;
417                 }
418             }
419             if (numFilteredScanResults != 0) {
420                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
421             }
422 
423             if (mLastScanSettings.singleScanEventHandler != null) {
424                 if (mLastScanSettings.reportSingleScanFullResults) {
425                     for (ScanResult scanResult : singleScanResults) {
426                         // ignore buckets scanned since there is only one bucket for a single scan
427                         mLastScanSettings.singleScanEventHandler.onFullScanResult(scanResult,
428                                 /* bucketsScanned */ 0);
429                     }
430                 }
431                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
432                 mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
433                         getScannedBandsInternal(mLastScanSettings.singleScanFreqs),
434                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
435                 mLastScanSettings.singleScanEventHandler
436                         .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
437             }
438 
439             mLastScanSettings = null;
440         }
441     }
442 
443 
444     @Override
getLatestBatchedScanResults(boolean flush)445     public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
446         return null;
447     }
448 
startHwPnoScan(WifiNative.PnoSettings pnoSettings)449     private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
450         return mWifiNative.startPnoScan(getIfaceName(), pnoSettings);
451     }
452 
stopHwPnoScan()453     private void stopHwPnoScan() {
454         mWifiNative.stopPnoScan(getIfaceName());
455     }
456 
457     /**
458      * Hw Pno Scan is required only for disconnected PNO when the device supports it.
459      * @param isConnectedPno Whether this is connected PNO vs disconnected PNO.
460      * @return true if HW PNO scan is required, false otherwise.
461      */
isHwPnoScanRequired(boolean isConnectedPno)462     private boolean isHwPnoScanRequired(boolean isConnectedPno) {
463         return (!isConnectedPno
464                 && mWifiGlobals.isBackgroundScanSupported());
465     }
466 
467     @Override
setHwPnoList(WifiNative.PnoSettings settings, WifiNative.PnoEventHandler eventHandler)468     public boolean setHwPnoList(WifiNative.PnoSettings settings,
469             WifiNative.PnoEventHandler eventHandler) {
470         synchronized (mSettingsLock) {
471             if (mLastPnoScanSettings != null) {
472                 Log.w(TAG, "Already running a PNO scan");
473                 return false;
474             }
475             if (!isHwPnoScanRequired(settings.isConnected)) {
476                 return false;
477             }
478 
479             mLastPnoScanSettings = new LastPnoScanSettings(
480                     mClock.getElapsedSinceBootNanos(),
481                     settings.networkList, eventHandler);
482 
483             if (!startHwPnoScan(settings)) {
484                 Log.e(TAG, "Failed to start PNO scan");
485                 reportPnoScanFailure();
486             }
487             return true;
488         }
489     }
490 
491     @Override
resetHwPnoList()492     public boolean resetHwPnoList() {
493         synchronized (mSettingsLock) {
494             if (mLastPnoScanSettings == null) {
495                 Log.w(TAG, "No PNO scan running");
496                 return false;
497             }
498             mLastPnoScanSettings = null;
499             // For wificond based PNO, we stop the scan immediately when we reset pno list.
500             stopHwPnoScan();
501             return true;
502         }
503     }
504 
505     @Override
isHwPnoSupported(boolean isConnectedPno)506     public boolean isHwPnoSupported(boolean isConnectedPno) {
507         // Hw Pno Scan is supported only for disconnected PNO when the device supports it.
508         return isHwPnoScanRequired(isConnectedPno);
509     }
510 
511     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)512     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
513         synchronized (mSettingsLock) {
514             long nowMs = mClock.getElapsedSinceBootMillis();
515             Log.d(TAG, "Latest native scan results nowMs = " + nowMs);
516             pw.println("Latest native scan results:");
517             if (mNativeScanResults != null) {
518                 List<ScanResult> scanResults = mNativeScanResults.stream().map(r -> {
519                     return r.getScanResult();
520                 }).collect(Collectors.toList());
521                 ScanResultUtil.dumpScanResults(pw, scanResults, nowMs);
522             }
523             pw.println("Latest native pno scan results:");
524             if (mNativePnoScanResults != null) {
525                 List<ScanResult> pnoScanResults = mNativePnoScanResults.stream().map(r -> {
526                     return r.getScanResult();
527                 }).collect(Collectors.toList());
528                 ScanResultUtil.dumpScanResults(pw, pnoScanResults, nowMs);
529             }
530             pw.println("Latest native scan results IEs:");
531             if (mNativeScanResults != null) {
532                 for (ScanDetail detail : mNativeScanResults) {
533                     if (detail.getInformationElementRawData() != null) {
534                         pw.println(NativeUtil.hexStringFromByteArray(
535                                 detail.getInformationElementRawData()));
536                     }
537                 }
538             }
539             pw.println("");
540         }
541     }
542 
543     private static class LastScanSettings {
LastScanSettings(long startTimeNanos, boolean reportSingleScanFullResults, ChannelCollection singleScanFreqs, WifiNative.ScanEventHandler singleScanEventHandler)544         LastScanSettings(long startTimeNanos,
545                 boolean reportSingleScanFullResults,
546                 ChannelCollection singleScanFreqs,
547                 WifiNative.ScanEventHandler singleScanEventHandler) {
548             this.startTimeNanos = startTimeNanos;
549             this.reportSingleScanFullResults = reportSingleScanFullResults;
550             this.singleScanFreqs = singleScanFreqs;
551             this.singleScanEventHandler = singleScanEventHandler;
552         }
553 
554         public long startTimeNanos;
555         public boolean reportSingleScanFullResults;
556         public ChannelCollection singleScanFreqs;
557         public WifiNative.ScanEventHandler singleScanEventHandler;
558 
559     }
560 
561     private static class LastPnoScanSettings {
LastPnoScanSettings(long startTimeNanos, WifiNative.PnoNetwork[] pnoNetworkList, WifiNative.PnoEventHandler pnoScanEventHandler)562         LastPnoScanSettings(long startTimeNanos,
563                 WifiNative.PnoNetwork[] pnoNetworkList,
564                 WifiNative.PnoEventHandler pnoScanEventHandler) {
565             this.startTimeNanos = startTimeNanos;
566             this.pnoNetworkList = pnoNetworkList;
567             this.pnoScanEventHandler = pnoScanEventHandler;
568         }
569 
570         public long startTimeNanos;
571         public WifiNative.PnoNetwork[] pnoNetworkList;
572         public WifiNative.PnoEventHandler pnoScanEventHandler;
573 
574     }
575 
576 }
577