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