Home | History | Annotate | Download | only in scanner
      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 
     17 package com.android.server.wifi.scanner;
     18 
     19 import static android.content.pm.PackageManager.PERMISSION_DENIED;
     20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
     21 
     22 import android.Manifest;
     23 import android.app.AlarmManager;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.net.wifi.IWifiScanner;
     29 import android.net.wifi.ScanResult;
     30 import android.net.wifi.WifiManager;
     31 import android.net.wifi.WifiScanner;
     32 import android.net.wifi.WifiScanner.ChannelSpec;
     33 import android.net.wifi.WifiScanner.PnoSettings;
     34 import android.net.wifi.WifiScanner.ScanData;
     35 import android.net.wifi.WifiScanner.ScanSettings;
     36 import android.os.Binder;
     37 import android.os.Bundle;
     38 import android.os.Looper;
     39 import android.os.Message;
     40 import android.os.Messenger;
     41 import android.os.RemoteException;
     42 import android.os.WorkSource;
     43 import android.util.ArrayMap;
     44 import android.util.LocalLog;
     45 import android.util.Log;
     46 import android.util.Pair;
     47 
     48 import com.android.internal.annotations.VisibleForTesting;
     49 import com.android.internal.app.IBatteryStats;
     50 import com.android.internal.util.ArrayUtils;
     51 import com.android.internal.util.AsyncChannel;
     52 import com.android.internal.util.Protocol;
     53 import com.android.internal.util.State;
     54 import com.android.internal.util.StateMachine;
     55 import com.android.server.wifi.Clock;
     56 import com.android.server.wifi.FrameworkFacade;
     57 import com.android.server.wifi.WifiInjector;
     58 import com.android.server.wifi.WifiLog;
     59 import com.android.server.wifi.WifiMetrics;
     60 import com.android.server.wifi.WifiNative;
     61 import com.android.server.wifi.WifiStateMachine;
     62 import com.android.server.wifi.nano.WifiMetricsProto;
     63 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
     64 import com.android.server.wifi.util.ScanResultUtil;
     65 import com.android.server.wifi.util.WifiHandler;
     66 
     67 import java.io.FileDescriptor;
     68 import java.io.PrintWriter;
     69 import java.util.ArrayList;
     70 import java.util.Arrays;
     71 import java.util.Collection;
     72 import java.util.Iterator;
     73 import java.util.List;
     74 
     75 public class WifiScanningServiceImpl extends IWifiScanner.Stub {
     76 
     77     private static final String TAG = WifiScanningService.TAG;
     78     private static final boolean DBG = false;
     79 
     80     private static final int UNKNOWN_PID = -1;
     81 
     82     private final LocalLog mLocalLog = new LocalLog(512);
     83 
     84     private WifiLog mLog;
     85 
     86     private void localLog(String message) {
     87         mLocalLog.log(message);
     88     }
     89 
     90     private void logw(String message) {
     91         Log.w(TAG, message);
     92         mLocalLog.log(message);
     93     }
     94 
     95     private void loge(String message) {
     96         Log.e(TAG, message);
     97         mLocalLog.log(message);
     98     }
     99 
    100     private WifiScannerImpl mScannerImpl;
    101 
    102     @Override
    103     public Messenger getMessenger() {
    104         if (mClientHandler != null) {
    105             mLog.trace("getMessenger() uid=%").c(Binder.getCallingUid()).flush();
    106             return new Messenger(mClientHandler);
    107         }
    108         loge("WifiScanningServiceImpl trying to get messenger w/o initialization");
    109         return null;
    110     }
    111 
    112     @Override
    113     public Bundle getAvailableChannels(int band) {
    114         mChannelHelper.updateChannels();
    115         ChannelSpec[] channelSpecs = mChannelHelper.getAvailableScanChannels(band);
    116         ArrayList<Integer> list = new ArrayList<Integer>(channelSpecs.length);
    117         for (ChannelSpec channelSpec : channelSpecs) {
    118             list.add(channelSpec.frequency);
    119         }
    120         Bundle b = new Bundle();
    121         b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list);
    122         mLog.trace("getAvailableChannels uid=%").c(Binder.getCallingUid()).flush();
    123         return b;
    124     }
    125 
    126     private void enforceLocationHardwarePermission(int uid) {
    127         mContext.enforcePermission(
    128                 Manifest.permission.LOCATION_HARDWARE,
    129                 UNKNOWN_PID, uid,
    130                 "LocationHardware");
    131     }
    132 
    133     private class ClientHandler extends WifiHandler {
    134 
    135         ClientHandler(String tag, Looper looper) {
    136             super(tag, looper);
    137         }
    138 
    139         @Override
    140         public void handleMessage(Message msg) {
    141             super.handleMessage(msg);
    142             switch (msg.what) {
    143                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
    144                     if (msg.replyTo == null) {
    145                         logw("msg.replyTo is null");
    146                         return;
    147                     }
    148                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
    149                     if (client != null) {
    150                         logw("duplicate client connection: " + msg.sendingUid + ", messenger="
    151                                 + msg.replyTo);
    152                         client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
    153                                 AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
    154                         return;
    155                     }
    156 
    157                     AsyncChannel ac = mFrameworkFacade.makeWifiAsyncChannel(TAG);
    158                     ac.connected(mContext, this, msg.replyTo);
    159 
    160                     client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac);
    161                     client.register();
    162 
    163                     ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
    164                             AsyncChannel.STATUS_SUCCESSFUL);
    165                     localLog("client connected: " + client);
    166                     return;
    167                 }
    168                 case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
    169                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
    170                     if (client != null) {
    171                         client.mChannel.disconnect();
    172                     }
    173                     return;
    174                 }
    175                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
    176                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
    177                     if (client != null && msg.arg1 != AsyncChannel.STATUS_SEND_UNSUCCESSFUL
    178                             && msg.arg1
    179                             != AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED) {
    180                         localLog("client disconnected: " + client + ", reason: " + msg.arg1);
    181                         client.cleanup();
    182                     }
    183                     return;
    184                 }
    185             }
    186 
    187             try {
    188                 enforceLocationHardwarePermission(msg.sendingUid);
    189             } catch (SecurityException e) {
    190                 localLog("failed to authorize app: " + e);
    191                 replyFailed(msg, WifiScanner.REASON_NOT_AUTHORIZED, "Not authorized");
    192                 return;
    193             }
    194 
    195             // Since the CMD_GET_SCAN_RESULTS and CMD_GET_SINGLE_SCAN_RESULTS messages are
    196             // sent from WifiScanner using |sendMessageSynchronously|, handle separately since
    197             // the |msg.replyTo| field does not actually correspond to the Messenger that is
    198             // registered for that client.
    199             if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) {
    200                 mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
    201                 return;
    202             }
    203             if (msg.what == WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS) {
    204                 mSingleScanStateMachine.sendMessage(Message.obtain(msg));
    205                 return;
    206             }
    207 
    208             ClientInfo ci = mClients.get(msg.replyTo);
    209             if (ci == null) {
    210                 loge("Could not find client info for message " + msg.replyTo + ", msg=" + msg);
    211                 replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
    212                 return;
    213             }
    214 
    215             switch (msg.what) {
    216                 case WifiScanner.CMD_START_BACKGROUND_SCAN:
    217                 case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
    218                     mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
    219                     break;
    220                 case WifiScanner.CMD_START_PNO_SCAN:
    221                 case WifiScanner.CMD_STOP_PNO_SCAN:
    222                     mPnoScanStateMachine.sendMessage(Message.obtain(msg));
    223                     break;
    224                 case WifiScanner.CMD_START_SINGLE_SCAN:
    225                 case WifiScanner.CMD_STOP_SINGLE_SCAN:
    226                     mSingleScanStateMachine.sendMessage(Message.obtain(msg));
    227                     break;
    228                 case WifiScanner.CMD_REGISTER_SCAN_LISTENER:
    229                     logScanRequest("registerScanListener", ci, msg.arg2, null, null, null);
    230                     mSingleScanListeners.addRequest(ci, msg.arg2, null, null);
    231                     replySucceeded(msg);
    232                     break;
    233                 case WifiScanner.CMD_DEREGISTER_SCAN_LISTENER:
    234                     logScanRequest("deregisterScanListener", ci, msg.arg2, null, null, null);
    235                     mSingleScanListeners.removeRequest(ci, msg.arg2);
    236                     break;
    237                 default:
    238                     replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request");
    239                     break;
    240             }
    241         }
    242     }
    243 
    244     private static final int BASE = Protocol.BASE_WIFI_SCANNER_SERVICE;
    245 
    246     private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
    247     private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
    248     private static final int CMD_DRIVER_LOADED                       = BASE + 6;
    249     private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
    250     private static final int CMD_SCAN_PAUSED                         = BASE + 8;
    251     private static final int CMD_SCAN_RESTARTED                      = BASE + 9;
    252     private static final int CMD_SCAN_FAILED                         = BASE + 10;
    253     private static final int CMD_PNO_NETWORK_FOUND                   = BASE + 11;
    254     private static final int CMD_PNO_SCAN_FAILED                     = BASE + 12;
    255 
    256     private final Context mContext;
    257     private final Looper mLooper;
    258     private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory;
    259     private final ArrayMap<Messenger, ClientInfo> mClients;
    260 
    261     private final RequestList<Void> mSingleScanListeners = new RequestList<>();
    262 
    263     private ChannelHelper mChannelHelper;
    264     private BackgroundScanScheduler mBackgroundScheduler;
    265     private WifiNative.ScanSettings mPreviousSchedule;
    266 
    267     private WifiBackgroundScanStateMachine mBackgroundScanStateMachine;
    268     private WifiSingleScanStateMachine mSingleScanStateMachine;
    269     private WifiPnoScanStateMachine mPnoScanStateMachine;
    270     private ClientHandler mClientHandler;
    271     private final IBatteryStats mBatteryStats;
    272     private final AlarmManager mAlarmManager;
    273     private final WifiMetrics mWifiMetrics;
    274     private final Clock mClock;
    275     private final FrameworkFacade mFrameworkFacade;
    276 
    277     WifiScanningServiceImpl(Context context, Looper looper,
    278             WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, IBatteryStats batteryStats,
    279             WifiInjector wifiInjector) {
    280         mContext = context;
    281         mLooper = looper;
    282         mScannerImplFactory = scannerImplFactory;
    283         mBatteryStats = batteryStats;
    284         mClients = new ArrayMap<>();
    285         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    286         mWifiMetrics = wifiInjector.getWifiMetrics();
    287         mClock = wifiInjector.getClock();
    288         mLog = wifiInjector.makeLog(TAG);
    289         mFrameworkFacade = wifiInjector.getFrameworkFacade();
    290         mPreviousSchedule = null;
    291     }
    292 
    293     public void startService() {
    294         mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper);
    295         mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper);
    296         mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper);
    297 
    298         mContext.registerReceiver(
    299                 new BroadcastReceiver() {
    300                     @Override
    301                     public void onReceive(Context context, Intent intent) {
    302                         int state = intent.getIntExtra(
    303                                 WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
    304                         if (DBG) localLog("SCAN_AVAILABLE : " + state);
    305                         if (state == WifiManager.WIFI_STATE_ENABLED) {
    306                             mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
    307                             mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
    308                             mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
    309                         } else if (state == WifiManager.WIFI_STATE_DISABLED) {
    310                             mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
    311                             mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
    312                             mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
    313                         }
    314                     }
    315                 }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
    316 
    317         mBackgroundScanStateMachine.start();
    318         mSingleScanStateMachine.start();
    319         mPnoScanStateMachine.start();
    320 
    321         // Create client handler only after StateMachines are ready.
    322         mClientHandler = new ClientHandler(TAG, mLooper);
    323     }
    324 
    325     /**
    326      * Provide a way for unit tests to set valid log object in the WifiHandler
    327      * @param log WifiLog object to assign to the clientHandler
    328      */
    329     @VisibleForTesting
    330     public void setWifiHandlerLogForTest(WifiLog log) {
    331         mClientHandler.setWifiLog(log);
    332     }
    333 
    334     private WorkSource computeWorkSource(ClientInfo ci, WorkSource requestedWorkSource) {
    335         if (requestedWorkSource != null) {
    336             requestedWorkSource.clearNames();
    337 
    338             if (!requestedWorkSource.isEmpty()) {
    339                 return requestedWorkSource;
    340             }
    341         }
    342 
    343         if (ci.getUid() > 0) {
    344             return new WorkSource(ci.getUid());
    345         }
    346 
    347         // We can't construct a sensible WorkSource because the one supplied to us was empty and
    348         // we don't have a valid UID for the given client.
    349         loge("Unable to compute workSource for client: " + ci + ", requested: "
    350                 + requestedWorkSource);
    351         return new WorkSource();
    352     }
    353 
    354     private class RequestInfo<T> {
    355         final ClientInfo clientInfo;
    356         final int handlerId;
    357         final WorkSource workSource;
    358         final T settings;
    359 
    360         RequestInfo(ClientInfo clientInfo, int handlerId, WorkSource requestedWorkSource,
    361                 T settings) {
    362             this.clientInfo = clientInfo;
    363             this.handlerId = handlerId;
    364             this.settings = settings;
    365             this.workSource = computeWorkSource(clientInfo, requestedWorkSource);
    366         }
    367 
    368         void reportEvent(int what, int arg1, Object obj) {
    369             clientInfo.reportEvent(what, arg1, handlerId, obj);
    370         }
    371     }
    372 
    373     private class RequestList<T> extends ArrayList<RequestInfo<T>> {
    374         void addRequest(ClientInfo ci, int handler, WorkSource reqworkSource, T settings) {
    375             add(new RequestInfo<T>(ci, handler, reqworkSource, settings));
    376         }
    377 
    378         T removeRequest(ClientInfo ci, int handlerId) {
    379             T removed = null;
    380             Iterator<RequestInfo<T>> iter = iterator();
    381             while (iter.hasNext()) {
    382                 RequestInfo<T> entry = iter.next();
    383                 if (entry.clientInfo == ci && entry.handlerId == handlerId) {
    384                     removed = entry.settings;
    385                     iter.remove();
    386                 }
    387             }
    388             return removed;
    389         }
    390 
    391         Collection<T> getAllSettings() {
    392             ArrayList<T> settingsList = new ArrayList<>();
    393             Iterator<RequestInfo<T>> iter = iterator();
    394             while (iter.hasNext()) {
    395                 RequestInfo<T> entry = iter.next();
    396                 settingsList.add(entry.settings);
    397             }
    398             return settingsList;
    399         }
    400 
    401         Collection<T> getAllSettingsForClient(ClientInfo ci) {
    402             ArrayList<T> settingsList = new ArrayList<>();
    403             Iterator<RequestInfo<T>> iter = iterator();
    404             while (iter.hasNext()) {
    405                 RequestInfo<T> entry = iter.next();
    406                 if (entry.clientInfo == ci) {
    407                     settingsList.add(entry.settings);
    408                 }
    409             }
    410             return settingsList;
    411         }
    412 
    413         void removeAllForClient(ClientInfo ci) {
    414             Iterator<RequestInfo<T>> iter = iterator();
    415             while (iter.hasNext()) {
    416                 RequestInfo<T> entry = iter.next();
    417                 if (entry.clientInfo == ci) {
    418                     iter.remove();
    419                 }
    420             }
    421         }
    422 
    423         WorkSource createMergedWorkSource() {
    424             WorkSource mergedSource = new WorkSource();
    425             for (RequestInfo<T> entry : this) {
    426                 mergedSource.add(entry.workSource);
    427             }
    428             return mergedSource;
    429         }
    430     }
    431 
    432     /**
    433      * State machine that holds the state of single scans. Scans should only be active in the
    434      * ScanningState. The pending scans and active scans maps are swapped when entering
    435      * ScanningState. Any requests queued while scanning will be placed in the pending queue and
    436      * executed after transitioning back to IdleState.
    437      */
    438     class WifiSingleScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler {
    439         /**
    440          * Maximum age of results that we return from our cache via
    441          * {@link WifiScanner#getScanResults()}.
    442          * This is currently set to 3 minutes to restore parity with the wpa_supplicant's scan
    443          * result cache expiration policy. (See b/62253332 for details)
    444          */
    445         @VisibleForTesting
    446         public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000;
    447 
    448         private final DefaultState mDefaultState = new DefaultState();
    449         private final DriverStartedState mDriverStartedState = new DriverStartedState();
    450         private final IdleState  mIdleState  = new IdleState();
    451         private final ScanningState  mScanningState  = new ScanningState();
    452 
    453         private WifiNative.ScanSettings mActiveScanSettings = null;
    454         private RequestList<ScanSettings> mActiveScans = new RequestList<>();
    455         private RequestList<ScanSettings> mPendingScans = new RequestList<>();
    456 
    457         // Scan results cached from the last full single scan request.
    458         private final List<ScanResult> mCachedScanResults = new ArrayList<>();
    459 
    460         WifiSingleScanStateMachine(Looper looper) {
    461             super("WifiSingleScanStateMachine", looper);
    462 
    463             setLogRecSize(128);
    464             setLogOnlyTransitions(false);
    465 
    466             // CHECKSTYLE:OFF IndentationCheck
    467             addState(mDefaultState);
    468                 addState(mDriverStartedState, mDefaultState);
    469                     addState(mIdleState, mDriverStartedState);
    470                     addState(mScanningState, mDriverStartedState);
    471             // CHECKSTYLE:ON IndentationCheck
    472 
    473             setInitialState(mDefaultState);
    474         }
    475 
    476         /**
    477          * Called to indicate a change in state for the current scan.
    478          * Will dispatch a coresponding event to the state machine
    479          */
    480         @Override
    481         public void onScanStatus(int event) {
    482             if (DBG) localLog("onScanStatus event received, event=" + event);
    483             switch(event) {
    484                 case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE:
    485                 case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS:
    486                 case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT:
    487                     sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
    488                     break;
    489                 case WifiNative.WIFI_SCAN_FAILED:
    490                     sendMessage(CMD_SCAN_FAILED);
    491                     break;
    492                 default:
    493                     Log.e(TAG, "Unknown scan status event: " + event);
    494                     break;
    495             }
    496         }
    497 
    498         /**
    499          * Called for each full scan result if requested
    500          */
    501         @Override
    502         public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) {
    503             if (DBG) localLog("onFullScanResult received");
    504             sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult);
    505         }
    506 
    507         @Override
    508         public void onScanPaused(ScanData[] scanData) {
    509             // should not happen for single scan
    510             Log.e(TAG, "Got scan paused for single scan");
    511         }
    512 
    513         @Override
    514         public void onScanRestarted() {
    515             // should not happen for single scan
    516             Log.e(TAG, "Got scan restarted for single scan");
    517         }
    518 
    519         class DefaultState extends State {
    520             @Override
    521             public void enter() {
    522                 mActiveScans.clear();
    523                 mPendingScans.clear();
    524             }
    525             @Override
    526             public boolean processMessage(Message msg) {
    527                 switch (msg.what) {
    528                     case CMD_DRIVER_LOADED:
    529                         if (mScannerImpl == null) {
    530                             loge("Failed to start single scan state machine because scanner impl"
    531                                     + " is null");
    532                             return HANDLED;
    533                         }
    534                         transitionTo(mIdleState);
    535                         return HANDLED;
    536                     case CMD_DRIVER_UNLOADED:
    537                         transitionTo(mDefaultState);
    538                         return HANDLED;
    539                     case WifiScanner.CMD_START_SINGLE_SCAN:
    540                     case WifiScanner.CMD_STOP_SINGLE_SCAN:
    541                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
    542                         return HANDLED;
    543                     case CMD_SCAN_RESULTS_AVAILABLE:
    544                         if (DBG) localLog("ignored scan results available event");
    545                         return HANDLED;
    546                     case CMD_FULL_SCAN_RESULTS:
    547                         if (DBG) localLog("ignored full scan result event");
    548                         return HANDLED;
    549                     case WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS:
    550                         msg.obj = new WifiScanner.ParcelableScanResults(
    551                             filterCachedScanResultsByAge());
    552                         replySucceeded(msg);
    553                         return HANDLED;
    554                     default:
    555                         return NOT_HANDLED;
    556                 }
    557             }
    558 
    559             /**
    560              * Filter out  any scan results that are older than
    561              * {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
    562              *
    563              * @return Filtered list of scan results.
    564              */
    565             private ScanResult[] filterCachedScanResultsByAge() {
    566                 // Using ScanResult.timestamp here to ensure that we use the same fields as
    567                 // WificondScannerImpl for filtering stale results.
    568                 long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
    569                 return mCachedScanResults.stream()
    570                         .filter(scanResult
    571                                 -> ((currentTimeInMillis - (scanResult.timestamp / 1000))
    572                                         < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS))
    573                         .toArray(ScanResult[]::new);
    574             }
    575         }
    576 
    577         /**
    578          * State representing when the driver is running. This state is not meant to be transitioned
    579          * directly, but is instead intended as a parent state of ScanningState and IdleState
    580          * to hold common functionality and handle cleaning up scans when the driver is shut down.
    581          */
    582         class DriverStartedState extends State {
    583             @Override
    584             public void exit() {
    585                 // clear scan results when scan mode is not active
    586                 mCachedScanResults.clear();
    587 
    588                 mWifiMetrics.incrementScanReturnEntry(
    589                         WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED,
    590                         mPendingScans.size());
    591                 sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
    592                         "Scan was interrupted");
    593             }
    594 
    595             @Override
    596             public boolean processMessage(Message msg) {
    597                 ClientInfo ci = mClients.get(msg.replyTo);
    598 
    599                 switch (msg.what) {
    600                     case CMD_DRIVER_LOADED:
    601                         // Ignore if we're already in driver loaded state.
    602                         return HANDLED;
    603                     case WifiScanner.CMD_START_SINGLE_SCAN:
    604                         mWifiMetrics.incrementOneshotScanCount();
    605                         int handler = msg.arg2;
    606                         Bundle scanParams = (Bundle) msg.obj;
    607                         if (scanParams == null) {
    608                             logCallback("singleScanInvalidRequest",  ci, handler, "null params");
    609                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
    610                             return HANDLED;
    611                         }
    612                         scanParams.setDefusable(true);
    613                         ScanSettings scanSettings =
    614                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
    615                         WorkSource workSource =
    616                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
    617                         if (validateScanRequest(ci, handler, scanSettings)) {
    618                             logScanRequest("addSingleScanRequest", ci, handler, workSource,
    619                                     scanSettings, null);
    620                             replySucceeded(msg);
    621 
    622                             // If there is an active scan that will fulfill the scan request then
    623                             // mark this request as an active scan, otherwise mark it pending.
    624                             // If were not currently scanning then try to start a scan. Otherwise
    625                             // this scan will be scheduled when transitioning back to IdleState
    626                             // after finishing the current scan.
    627                             if (getCurrentState() == mScanningState) {
    628                                 if (activeScanSatisfies(scanSettings)) {
    629                                     mActiveScans.addRequest(ci, handler, workSource, scanSettings);
    630                                 } else {
    631                                     mPendingScans.addRequest(ci, handler, workSource, scanSettings);
    632                                 }
    633                             } else {
    634                                 mPendingScans.addRequest(ci, handler, workSource, scanSettings);
    635                                 tryToStartNewScan();
    636                             }
    637                         } else {
    638                             logCallback("singleScanInvalidRequest",  ci, handler, "bad request");
    639                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
    640                             mWifiMetrics.incrementScanReturnEntry(
    641                                     WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
    642                         }
    643                         return HANDLED;
    644                     case WifiScanner.CMD_STOP_SINGLE_SCAN:
    645                         removeSingleScanRequest(ci, msg.arg2);
    646                         return HANDLED;
    647                     default:
    648                         return NOT_HANDLED;
    649                 }
    650             }
    651         }
    652 
    653         class IdleState extends State {
    654             @Override
    655             public void enter() {
    656                 tryToStartNewScan();
    657             }
    658 
    659             @Override
    660             public boolean processMessage(Message msg) {
    661                 return NOT_HANDLED;
    662             }
    663         }
    664 
    665         class ScanningState extends State {
    666             private WorkSource mScanWorkSource;
    667 
    668             @Override
    669             public void enter() {
    670                 mScanWorkSource = mActiveScans.createMergedWorkSource();
    671                 try {
    672                     mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource);
    673                 } catch (RemoteException e) {
    674                     loge(e.toString());
    675                 }
    676             }
    677 
    678             @Override
    679             public void exit() {
    680                 mActiveScanSettings = null;
    681                 try {
    682                     mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource);
    683                 } catch (RemoteException e) {
    684                     loge(e.toString());
    685                 }
    686 
    687                 // if any scans are still active (never got results available then indicate failure)
    688                 mWifiMetrics.incrementScanReturnEntry(
    689                                 WifiMetricsProto.WifiLog.SCAN_UNKNOWN,
    690                                 mActiveScans.size());
    691                 sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED,
    692                         "Scan was interrupted");
    693             }
    694 
    695             @Override
    696             public boolean processMessage(Message msg) {
    697                 switch (msg.what) {
    698                     case CMD_SCAN_RESULTS_AVAILABLE:
    699                         mWifiMetrics.incrementScanReturnEntry(
    700                                 WifiMetricsProto.WifiLog.SCAN_SUCCESS,
    701                                 mActiveScans.size());
    702                         reportScanResults(mScannerImpl.getLatestSingleScanResults());
    703                         mActiveScans.clear();
    704                         transitionTo(mIdleState);
    705                         return HANDLED;
    706                     case CMD_FULL_SCAN_RESULTS:
    707                         reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2);
    708                         return HANDLED;
    709                     case CMD_SCAN_FAILED:
    710                         mWifiMetrics.incrementScanReturnEntry(
    711                                 WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size());
    712                         sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED,
    713                                 "Scan failed");
    714                         transitionTo(mIdleState);
    715                         return HANDLED;
    716                     default:
    717                         return NOT_HANDLED;
    718                 }
    719             }
    720         }
    721 
    722         boolean validateScanType(int type) {
    723             return (type == WifiScanner.TYPE_LOW_LATENCY || type == WifiScanner.TYPE_LOW_POWER
    724                     || type == WifiScanner.TYPE_HIGH_ACCURACY);
    725         }
    726 
    727         boolean validateScanRequest(ClientInfo ci, int handler, ScanSettings settings) {
    728             if (ci == null) {
    729                 Log.d(TAG, "Failing single scan request ClientInfo not found " + handler);
    730                 return false;
    731             }
    732             if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
    733                 if (settings.channels == null || settings.channels.length == 0) {
    734                     Log.d(TAG, "Failing single scan because channel list was empty");
    735                     return false;
    736                 }
    737             }
    738             if (!validateScanType(settings.type)) {
    739                 Log.e(TAG, "Invalid scan type " + settings.type);
    740                 return false;
    741             }
    742             if (mContext.checkPermission(
    743                     Manifest.permission.NETWORK_STACK, UNKNOWN_PID, ci.getUid())
    744                     == PERMISSION_DENIED) {
    745                 if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) {
    746                     Log.e(TAG, "Failing single scan because app " + ci.getUid()
    747                             + " does not have permission to set hidden networks");
    748                     return false;
    749                 }
    750                 if (settings.type != WifiScanner.TYPE_LOW_LATENCY) {
    751                     Log.e(TAG, "Failing single scan because app " + ci.getUid()
    752                             + " does not have permission to set type");
    753                     return false;
    754                 }
    755             }
    756             return true;
    757         }
    758 
    759         int getNativeScanType(int type) {
    760             switch(type) {
    761                 case WifiScanner.TYPE_LOW_LATENCY:
    762                     return WifiNative.SCAN_TYPE_LOW_LATENCY;
    763                 case WifiScanner.TYPE_LOW_POWER:
    764                     return WifiNative.SCAN_TYPE_LOW_POWER;
    765                 case WifiScanner.TYPE_HIGH_ACCURACY:
    766                     return WifiNative.SCAN_TYPE_HIGH_ACCURACY;
    767                 default:
    768                     // This should never happen becuase we've validated the incoming type in
    769                     // |validateScanType|.
    770                     throw new IllegalArgumentException("Invalid scan type " + type);
    771             }
    772         }
    773 
    774         // We can coalesce a LOW_POWER/LOW_LATENCY scan request into an ongoing HIGH_ACCURACY
    775         // scan request. But, we can't coalesce a HIGH_ACCURACY scan request into an ongoing
    776         // LOW_POWER/LOW_LATENCY scan request.
    777         boolean activeScanTypeSatisfies(int requestScanType) {
    778             switch(mActiveScanSettings.scanType) {
    779                 case WifiNative.SCAN_TYPE_LOW_LATENCY:
    780                 case WifiNative.SCAN_TYPE_LOW_POWER:
    781                     return requestScanType != WifiNative.SCAN_TYPE_HIGH_ACCURACY;
    782                 case WifiNative.SCAN_TYPE_HIGH_ACCURACY:
    783                     return true;
    784                 default:
    785                     // This should never happen becuase we've validated the incoming type in
    786                     // |validateScanType|.
    787                     throw new IllegalArgumentException("Invalid scan type "
    788                         + mActiveScanSettings.scanType);
    789             }
    790         }
    791 
    792         // If there is a HIGH_ACCURACY scan request among the requests being merged, the merged
    793         // scan type should be HIGH_ACCURACY.
    794         int mergeScanTypes(int existingScanType, int newScanType) {
    795             switch(existingScanType) {
    796                 case WifiNative.SCAN_TYPE_LOW_LATENCY:
    797                 case WifiNative.SCAN_TYPE_LOW_POWER:
    798                     return newScanType;
    799                 case WifiNative.SCAN_TYPE_HIGH_ACCURACY:
    800                     return existingScanType;
    801                 default:
    802                     // This should never happen becuase we've validated the incoming type in
    803                     // |validateScanType|.
    804                     throw new IllegalArgumentException("Invalid scan type " + existingScanType);
    805             }
    806         }
    807 
    808         boolean activeScanSatisfies(ScanSettings settings) {
    809             if (mActiveScanSettings == null) {
    810                 return false;
    811             }
    812 
    813             if (!activeScanTypeSatisfies(getNativeScanType(settings.type))) {
    814                 return false;
    815             }
    816 
    817             // there is always one bucket for a single scan
    818             WifiNative.BucketSettings activeBucket = mActiveScanSettings.buckets[0];
    819 
    820             // validate that all requested channels are being scanned
    821             ChannelCollection activeChannels = mChannelHelper.createChannelCollection();
    822             activeChannels.addChannels(activeBucket);
    823             if (!activeChannels.containsSettings(settings)) {
    824                 return false;
    825             }
    826 
    827             // if the request is for a full scan, but there is no ongoing full scan
    828             if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
    829                     && (activeBucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
    830                     == 0) {
    831                 return false;
    832             }
    833 
    834             if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) {
    835                 if (ArrayUtils.isEmpty(mActiveScanSettings.hiddenNetworks)) {
    836                     return false;
    837                 }
    838                 List<WifiNative.HiddenNetwork> activeHiddenNetworks = new ArrayList<>();
    839                 for (WifiNative.HiddenNetwork hiddenNetwork : mActiveScanSettings.hiddenNetworks) {
    840                     activeHiddenNetworks.add(hiddenNetwork);
    841                 }
    842                 for (ScanSettings.HiddenNetwork hiddenNetwork : settings.hiddenNetworks) {
    843                     WifiNative.HiddenNetwork nativeHiddenNetwork = new WifiNative.HiddenNetwork();
    844                     nativeHiddenNetwork.ssid = hiddenNetwork.ssid;
    845                     if (!activeHiddenNetworks.contains(nativeHiddenNetwork)) {
    846                         return false;
    847                     }
    848                 }
    849             }
    850 
    851             return true;
    852         }
    853 
    854         void removeSingleScanRequest(ClientInfo ci, int handler) {
    855             if (ci != null) {
    856                 logScanRequest("removeSingleScanRequest", ci, handler, null, null, null);
    857                 mPendingScans.removeRequest(ci, handler);
    858                 mActiveScans.removeRequest(ci, handler);
    859             }
    860         }
    861 
    862         void removeSingleScanRequests(ClientInfo ci) {
    863             if (ci != null) {
    864                 logScanRequest("removeSingleScanRequests", ci, -1, null, null, null);
    865                 mPendingScans.removeAllForClient(ci);
    866                 mActiveScans.removeAllForClient(ci);
    867             }
    868         }
    869 
    870         void tryToStartNewScan() {
    871             if (mPendingScans.size() == 0) { // no pending requests
    872                 return;
    873             }
    874             mChannelHelper.updateChannels();
    875             // TODO move merging logic to a scheduler
    876             WifiNative.ScanSettings settings = new WifiNative.ScanSettings();
    877             settings.num_buckets = 1;
    878             WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
    879             bucketSettings.bucket = 0;
    880             bucketSettings.period_ms = 0;
    881             bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
    882 
    883             ChannelCollection channels = mChannelHelper.createChannelCollection();
    884             List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
    885             for (RequestInfo<ScanSettings> entry : mPendingScans) {
    886                 settings.scanType =
    887                     mergeScanTypes(settings.scanType, getNativeScanType(entry.settings.type));
    888                 channels.addChannels(entry.settings);
    889                 if (entry.settings.hiddenNetworks != null) {
    890                     for (int i = 0; i < entry.settings.hiddenNetworks.length; i++) {
    891                         WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork();
    892                         hiddenNetwork.ssid = entry.settings.hiddenNetworks[i].ssid;
    893                         hiddenNetworkList.add(hiddenNetwork);
    894                     }
    895                 }
    896                 if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
    897                         != 0) {
    898                     bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
    899                 }
    900             }
    901             if (hiddenNetworkList.size() > 0) {
    902                 settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()];
    903                 int numHiddenNetworks = 0;
    904                 for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) {
    905                     settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork;
    906                 }
    907             }
    908 
    909             channels.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
    910 
    911             settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
    912             if (mScannerImpl.startSingleScan(settings, this)) {
    913                 // store the active scan settings
    914                 mActiveScanSettings = settings;
    915                 // swap pending and active scan requests
    916                 RequestList<ScanSettings> tmp = mActiveScans;
    917                 mActiveScans = mPendingScans;
    918                 mPendingScans = tmp;
    919                 // make sure that the pending list is clear
    920                 mPendingScans.clear();
    921                 transitionTo(mScanningState);
    922             } else {
    923                 mWifiMetrics.incrementScanReturnEntry(
    924                         WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mPendingScans.size());
    925                 // notify and cancel failed scans
    926                 sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
    927                         "Failed to start single scan");
    928             }
    929         }
    930 
    931         void sendOpFailedToAllAndClear(RequestList<?> clientHandlers, int reason,
    932                 String description) {
    933             for (RequestInfo<?> entry : clientHandlers) {
    934                 logCallback("singleScanFailed",  entry.clientInfo, entry.handlerId,
    935                         "reason=" + reason + ", " + description);
    936                 entry.reportEvent(WifiScanner.CMD_OP_FAILED, 0,
    937                         new WifiScanner.OperationResult(reason, description));
    938             }
    939             clientHandlers.clear();
    940         }
    941 
    942         void reportFullScanResult(ScanResult result, int bucketsScanned) {
    943             for (RequestInfo<ScanSettings> entry : mActiveScans) {
    944                 if (ScanScheduleUtil.shouldReportFullScanResultForSettings(mChannelHelper,
    945                                 result, bucketsScanned, entry.settings, -1)) {
    946                     entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result);
    947                 }
    948             }
    949 
    950             for (RequestInfo<Void> entry : mSingleScanListeners) {
    951                 entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result);
    952             }
    953         }
    954 
    955         void reportScanResults(ScanData results) {
    956             if (results != null && results.getResults() != null) {
    957                 if (results.getResults().length > 0) {
    958                     mWifiMetrics.incrementNonEmptyScanResultCount();
    959                 } else {
    960                     mWifiMetrics.incrementEmptyScanResultCount();
    961                 }
    962             }
    963             ScanData[] allResults = new ScanData[] {results};
    964             for (RequestInfo<ScanSettings> entry : mActiveScans) {
    965                 ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings(
    966                         mChannelHelper, allResults, entry.settings, -1);
    967                 WifiScanner.ParcelableScanData parcelableResultsToDeliver =
    968                         new WifiScanner.ParcelableScanData(resultsToDeliver);
    969                 logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
    970                         describeForLog(resultsToDeliver));
    971                 entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableResultsToDeliver);
    972                 // make sure the handler is removed
    973                 entry.reportEvent(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, null);
    974             }
    975 
    976             WifiScanner.ParcelableScanData parcelableAllResults =
    977                     new WifiScanner.ParcelableScanData(allResults);
    978             for (RequestInfo<Void> entry : mSingleScanListeners) {
    979                 logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
    980                         describeForLog(allResults));
    981                 entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
    982             }
    983 
    984             if (results.isAllChannelsScanned()) {
    985                 mCachedScanResults.clear();
    986                 mCachedScanResults.addAll(Arrays.asList(results.getResults()));
    987             }
    988         }
    989 
    990         List<ScanResult> getCachedScanResultsAsList() {
    991             return mCachedScanResults;
    992         }
    993     }
    994 
    995     class WifiBackgroundScanStateMachine extends StateMachine
    996             implements WifiNative.ScanEventHandler {
    997 
    998         private final DefaultState mDefaultState = new DefaultState();
    999         private final StartedState mStartedState = new StartedState();
   1000         private final PausedState  mPausedState  = new PausedState();
   1001 
   1002         private final RequestList<ScanSettings> mActiveBackgroundScans = new RequestList<>();
   1003 
   1004         WifiBackgroundScanStateMachine(Looper looper) {
   1005             super("WifiBackgroundScanStateMachine", looper);
   1006 
   1007             setLogRecSize(512);
   1008             setLogOnlyTransitions(false);
   1009 
   1010             // CHECKSTYLE:OFF IndentationCheck
   1011             addState(mDefaultState);
   1012                 addState(mStartedState, mDefaultState);
   1013                 addState(mPausedState, mDefaultState);
   1014             // CHECKSTYLE:ON IndentationCheck
   1015 
   1016             setInitialState(mDefaultState);
   1017         }
   1018 
   1019         public Collection<ScanSettings> getBackgroundScanSettings(ClientInfo ci) {
   1020             return mActiveBackgroundScans.getAllSettingsForClient(ci);
   1021         }
   1022 
   1023         public void removeBackgroundScanSettings(ClientInfo ci) {
   1024             mActiveBackgroundScans.removeAllForClient(ci);
   1025             updateSchedule();
   1026         }
   1027 
   1028         @Override
   1029         public void onScanStatus(int event) {
   1030             if (DBG) localLog("onScanStatus event received, event=" + event);
   1031             switch(event) {
   1032                 case WifiNative.WIFI_SCAN_RESULTS_AVAILABLE:
   1033                 case WifiNative.WIFI_SCAN_THRESHOLD_NUM_SCANS:
   1034                 case WifiNative.WIFI_SCAN_THRESHOLD_PERCENT:
   1035                     sendMessage(CMD_SCAN_RESULTS_AVAILABLE);
   1036                     break;
   1037                 case WifiNative.WIFI_SCAN_FAILED:
   1038                     sendMessage(CMD_SCAN_FAILED);
   1039                     break;
   1040                 default:
   1041                     Log.e(TAG, "Unknown scan status event: " + event);
   1042                     break;
   1043             }
   1044         }
   1045 
   1046         @Override
   1047         public void onFullScanResult(ScanResult fullScanResult, int bucketsScanned) {
   1048             if (DBG) localLog("onFullScanResult received");
   1049             sendMessage(CMD_FULL_SCAN_RESULTS, 0, bucketsScanned, fullScanResult);
   1050         }
   1051 
   1052         @Override
   1053         public void onScanPaused(ScanData scanData[]) {
   1054             if (DBG) localLog("onScanPaused received");
   1055             sendMessage(CMD_SCAN_PAUSED, scanData);
   1056         }
   1057 
   1058         @Override
   1059         public void onScanRestarted() {
   1060             if (DBG) localLog("onScanRestarted received");
   1061             sendMessage(CMD_SCAN_RESTARTED);
   1062         }
   1063 
   1064         class DefaultState extends State {
   1065             @Override
   1066             public void enter() {
   1067                 if (DBG) localLog("DefaultState");
   1068                 mActiveBackgroundScans.clear();
   1069             }
   1070 
   1071             @Override
   1072             public boolean processMessage(Message msg) {
   1073                 switch (msg.what) {
   1074                     case CMD_DRIVER_LOADED:
   1075                         // TODO this should be moved to a common location since it is used outside
   1076                         // of this state machine. It is ok right now because the driver loaded event
   1077                         // is sent to this state machine first.
   1078                         mScannerImpl = mScannerImplFactory.create(mContext, mLooper, mClock);
   1079                         if (mScannerImpl == null) {
   1080                             loge("Failed to start bgscan scan state machine because scanner impl"
   1081                                     + " is null");
   1082                             return HANDLED;
   1083                         }
   1084                         mChannelHelper = mScannerImpl.getChannelHelper();
   1085 
   1086                         mBackgroundScheduler = new BackgroundScanScheduler(mChannelHelper);
   1087 
   1088                         WifiNative.ScanCapabilities capabilities =
   1089                                 new WifiNative.ScanCapabilities();
   1090                         if (!mScannerImpl.getScanCapabilities(capabilities)) {
   1091                             loge("could not get scan capabilities");
   1092                             return HANDLED;
   1093                         }
   1094                         if (capabilities.max_scan_buckets <= 0) {
   1095                             loge("invalid max buckets in scan capabilities "
   1096                                     + capabilities.max_scan_buckets);
   1097                             return HANDLED;
   1098                         }
   1099                         mBackgroundScheduler.setMaxBuckets(capabilities.max_scan_buckets);
   1100                         mBackgroundScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan);
   1101 
   1102                         Log.i(TAG, "wifi driver loaded with scan capabilities: "
   1103                                 + "max buckets=" + capabilities.max_scan_buckets);
   1104 
   1105                         transitionTo(mStartedState);
   1106                         return HANDLED;
   1107                     case CMD_DRIVER_UNLOADED:
   1108                         Log.i(TAG, "wifi driver unloaded");
   1109                         transitionTo(mDefaultState);
   1110                         break;
   1111                     case WifiScanner.CMD_START_BACKGROUND_SCAN:
   1112                     case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
   1113                     case WifiScanner.CMD_START_SINGLE_SCAN:
   1114                     case WifiScanner.CMD_STOP_SINGLE_SCAN:
   1115                     case WifiScanner.CMD_GET_SCAN_RESULTS:
   1116                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
   1117                         break;
   1118 
   1119                     case CMD_SCAN_RESULTS_AVAILABLE:
   1120                         if (DBG) localLog("ignored scan results available event");
   1121                         break;
   1122 
   1123                     case CMD_FULL_SCAN_RESULTS:
   1124                         if (DBG) localLog("ignored full scan result event");
   1125                         break;
   1126 
   1127                     default:
   1128                         break;
   1129                 }
   1130 
   1131                 return HANDLED;
   1132             }
   1133         }
   1134 
   1135         class StartedState extends State {
   1136 
   1137             @Override
   1138             public void enter() {
   1139                 if (DBG) localLog("StartedState");
   1140             }
   1141 
   1142             @Override
   1143             public void exit() {
   1144                 sendBackgroundScanFailedToAllAndClear(
   1145                         WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted");
   1146                 if (mScannerImpl != null) {
   1147                     mScannerImpl.cleanup();
   1148                 }
   1149             }
   1150 
   1151             @Override
   1152             public boolean processMessage(Message msg) {
   1153                 ClientInfo ci = mClients.get(msg.replyTo);
   1154 
   1155                 switch (msg.what) {
   1156                     case CMD_DRIVER_LOADED:
   1157                         Log.e(TAG, "wifi driver loaded received while already loaded");
   1158                         // Ignore if we're already in driver loaded state.
   1159                         return HANDLED;
   1160                     case CMD_DRIVER_UNLOADED:
   1161                         return NOT_HANDLED;
   1162                     case WifiScanner.CMD_START_BACKGROUND_SCAN: {
   1163                         mWifiMetrics.incrementBackgroundScanCount();
   1164                         Bundle scanParams = (Bundle) msg.obj;
   1165                         if (scanParams == null) {
   1166                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
   1167                             return HANDLED;
   1168                         }
   1169                         scanParams.setDefusable(true);
   1170                         ScanSettings scanSettings =
   1171                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
   1172                         WorkSource workSource =
   1173                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
   1174                         if (addBackgroundScanRequest(ci, msg.arg2, scanSettings, workSource)) {
   1175                             replySucceeded(msg);
   1176                         } else {
   1177                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
   1178                         }
   1179                         break;
   1180                     }
   1181                     case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
   1182                         removeBackgroundScanRequest(ci, msg.arg2);
   1183                         break;
   1184                     case WifiScanner.CMD_GET_SCAN_RESULTS:
   1185                         reportScanResults(mScannerImpl.getLatestBatchedScanResults(true));
   1186                         replySucceeded(msg);
   1187                         break;
   1188                     case CMD_SCAN_RESULTS_AVAILABLE:
   1189                         reportScanResults(mScannerImpl.getLatestBatchedScanResults(true));
   1190                         break;
   1191                     case CMD_FULL_SCAN_RESULTS:
   1192                         reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2);
   1193                         break;
   1194                     case CMD_SCAN_PAUSED:
   1195                         reportScanResults((ScanData[]) msg.obj);
   1196                         transitionTo(mPausedState);
   1197                         break;
   1198                     case CMD_SCAN_FAILED:
   1199                         Log.e(TAG, "WifiScanner background scan gave CMD_SCAN_FAILED");
   1200                         sendBackgroundScanFailedToAllAndClear(
   1201                                 WifiScanner.REASON_UNSPECIFIED, "Background Scan failed");
   1202                         break;
   1203                     default:
   1204                         return NOT_HANDLED;
   1205                 }
   1206 
   1207                 return HANDLED;
   1208             }
   1209         }
   1210 
   1211         class PausedState extends State {
   1212             @Override
   1213             public void enter() {
   1214                 if (DBG) localLog("PausedState");
   1215             }
   1216 
   1217             @Override
   1218             public boolean processMessage(Message msg) {
   1219                 switch (msg.what) {
   1220                     case CMD_SCAN_RESTARTED:
   1221                         transitionTo(mStartedState);
   1222                         break;
   1223                     default:
   1224                         deferMessage(msg);
   1225                         break;
   1226                 }
   1227                 return HANDLED;
   1228             }
   1229         }
   1230 
   1231         private boolean addBackgroundScanRequest(ClientInfo ci, int handler,
   1232                 ScanSettings settings, WorkSource workSource) {
   1233             // sanity check the input
   1234             if (ci == null) {
   1235                 Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
   1236                 return false;
   1237             }
   1238             if (settings.periodInMs < WifiScanner.MIN_SCAN_PERIOD_MS) {
   1239                 loge("Failing scan request because periodInMs is " + settings.periodInMs
   1240                         + ", min scan period is: " + WifiScanner.MIN_SCAN_PERIOD_MS);
   1241                 return false;
   1242             }
   1243 
   1244             if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED && settings.channels == null) {
   1245                 loge("Channels was null with unspecified band");
   1246                 return false;
   1247             }
   1248 
   1249             if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED
   1250                     && settings.channels.length == 0) {
   1251                 loge("No channels specified");
   1252                 return false;
   1253             }
   1254 
   1255             int minSupportedPeriodMs = mChannelHelper.estimateScanDuration(settings);
   1256             if (settings.periodInMs < minSupportedPeriodMs) {
   1257                 loge("Failing scan request because minSupportedPeriodMs is "
   1258                         + minSupportedPeriodMs + " but the request wants " + settings.periodInMs);
   1259                 return false;
   1260             }
   1261 
   1262             // check truncated binary exponential back off scan settings
   1263             if (settings.maxPeriodInMs != 0 && settings.maxPeriodInMs != settings.periodInMs) {
   1264                 if (settings.maxPeriodInMs < settings.periodInMs) {
   1265                     loge("Failing scan request because maxPeriodInMs is " + settings.maxPeriodInMs
   1266                             + " but less than periodInMs " + settings.periodInMs);
   1267                     return false;
   1268                 }
   1269                 if (settings.maxPeriodInMs > WifiScanner.MAX_SCAN_PERIOD_MS) {
   1270                     loge("Failing scan request because maxSupportedPeriodMs is "
   1271                             + WifiScanner.MAX_SCAN_PERIOD_MS + " but the request wants "
   1272                             + settings.maxPeriodInMs);
   1273                     return false;
   1274                 }
   1275                 if (settings.stepCount < 1) {
   1276                     loge("Failing scan request because stepCount is " + settings.stepCount
   1277                             + " which is less than 1");
   1278                     return false;
   1279                 }
   1280             }
   1281 
   1282             logScanRequest("addBackgroundScanRequest", ci, handler, null, settings, null);
   1283             mActiveBackgroundScans.addRequest(ci, handler, workSource, settings);
   1284 
   1285             if (updateSchedule()) {
   1286                 return true;
   1287             } else {
   1288                 mActiveBackgroundScans.removeRequest(ci, handler);
   1289                 localLog("Failing scan request because failed to reset scan");
   1290                 return false;
   1291             }
   1292         }
   1293 
   1294         private boolean updateSchedule() {
   1295             if (mChannelHelper == null || mBackgroundScheduler == null || mScannerImpl == null) {
   1296                 loge("Failed to update schedule because WifiScanningService is not initialized");
   1297                 return false;
   1298             }
   1299             mChannelHelper.updateChannels();
   1300             Collection<ScanSettings> settings = mActiveBackgroundScans.getAllSettings();
   1301 
   1302             mBackgroundScheduler.updateSchedule(settings);
   1303             WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule();
   1304 
   1305             if (ScanScheduleUtil.scheduleEquals(mPreviousSchedule, schedule)) {
   1306                 if (DBG) Log.d(TAG, "schedule updated with no change");
   1307                 return true;
   1308             }
   1309 
   1310             mPreviousSchedule = schedule;
   1311 
   1312             if (schedule.num_buckets == 0) {
   1313                 mScannerImpl.stopBatchedScan();
   1314                 if (DBG) Log.d(TAG, "scan stopped");
   1315                 return true;
   1316             } else {
   1317                 localLog("starting scan: "
   1318                         + "base period=" + schedule.base_period_ms
   1319                         + ", max ap per scan=" + schedule.max_ap_per_scan
   1320                         + ", batched scans=" + schedule.report_threshold_num_scans);
   1321                 for (int b = 0; b < schedule.num_buckets; b++) {
   1322                     WifiNative.BucketSettings bucket = schedule.buckets[b];
   1323                     localLog("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)"
   1324                             + "[" + bucket.report_events + "]: "
   1325                             + ChannelHelper.toString(bucket));
   1326                 }
   1327 
   1328                 if (mScannerImpl.startBatchedScan(schedule, this)) {
   1329                     if (DBG) {
   1330                         Log.d(TAG, "scan restarted with " + schedule.num_buckets
   1331                                 + " bucket(s) and base period: " + schedule.base_period_ms);
   1332                     }
   1333                     return true;
   1334                 } else {
   1335                     mPreviousSchedule = null;
   1336                     loge("error starting scan: "
   1337                             + "base period=" + schedule.base_period_ms
   1338                             + ", max ap per scan=" + schedule.max_ap_per_scan
   1339                             + ", batched scans=" + schedule.report_threshold_num_scans);
   1340                     for (int b = 0; b < schedule.num_buckets; b++) {
   1341                         WifiNative.BucketSettings bucket = schedule.buckets[b];
   1342                         loge("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)"
   1343                                 + "[" + bucket.report_events + "]: "
   1344                                 + ChannelHelper.toString(bucket));
   1345                     }
   1346                     return false;
   1347                 }
   1348             }
   1349         }
   1350 
   1351         private void removeBackgroundScanRequest(ClientInfo ci, int handler) {
   1352             if (ci != null) {
   1353                 ScanSettings settings = mActiveBackgroundScans.removeRequest(ci, handler);
   1354                 logScanRequest("removeBackgroundScanRequest", ci, handler, null, settings, null);
   1355                 updateSchedule();
   1356             }
   1357         }
   1358 
   1359         private void reportFullScanResult(ScanResult result, int bucketsScanned) {
   1360             for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) {
   1361                 ClientInfo ci = entry.clientInfo;
   1362                 int handler = entry.handlerId;
   1363                 ScanSettings settings = entry.settings;
   1364                 if (mBackgroundScheduler.shouldReportFullScanResultForSettings(
   1365                                 result, bucketsScanned, settings)) {
   1366                     ScanResult newResult = new ScanResult(result);
   1367                     if (result.informationElements != null) {
   1368                         newResult.informationElements = result.informationElements.clone();
   1369                     }
   1370                     else {
   1371                         newResult.informationElements = null;
   1372                     }
   1373                     ci.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, handler, newResult);
   1374                 }
   1375             }
   1376         }
   1377 
   1378         private void reportScanResults(ScanData[] results) {
   1379             if (results == null) {
   1380                 Log.d(TAG,"The results is null, nothing to report.");
   1381                 return;
   1382             }
   1383             for (ScanData result : results) {
   1384                 if (result != null && result.getResults() != null) {
   1385                     if (result.getResults().length > 0) {
   1386                         mWifiMetrics.incrementNonEmptyScanResultCount();
   1387                     } else {
   1388                         mWifiMetrics.incrementEmptyScanResultCount();
   1389                     }
   1390                 }
   1391             }
   1392             for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) {
   1393                 ClientInfo ci = entry.clientInfo;
   1394                 int handler = entry.handlerId;
   1395                 ScanSettings settings = entry.settings;
   1396                 ScanData[] resultsToDeliver =
   1397                         mBackgroundScheduler.filterResultsForSettings(results, settings);
   1398                 if (resultsToDeliver != null) {
   1399                     logCallback("backgroundScanResults", ci, handler,
   1400                             describeForLog(resultsToDeliver));
   1401                     WifiScanner.ParcelableScanData parcelableScanData =
   1402                             new WifiScanner.ParcelableScanData(resultsToDeliver);
   1403                     ci.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, handler, parcelableScanData);
   1404                 }
   1405             }
   1406         }
   1407 
   1408         private void sendBackgroundScanFailedToAllAndClear(int reason, String description) {
   1409             for (RequestInfo<ScanSettings> entry : mActiveBackgroundScans) {
   1410                 ClientInfo ci = entry.clientInfo;
   1411                 int handler = entry.handlerId;
   1412                 ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler,
   1413                         new WifiScanner.OperationResult(reason, description));
   1414             }
   1415             mActiveBackgroundScans.clear();
   1416         }
   1417     }
   1418 
   1419     /**
   1420      * PNO scan state machine has 5 states:
   1421      * -Default State
   1422      *   -Started State
   1423      *     -Hw Pno Scan state
   1424      *       -Single Scan state
   1425      *
   1426      * These are the main state transitions:
   1427      * 1. Start at |Default State|
   1428      * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager.
   1429      * 3. When a new PNO scan request comes in:
   1430      *   a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO
   1431      *        (This could either be HAL based ePNO or wificond based PNO).
   1432      *   a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result
   1433      *        contains IE (information elements). If yes, send the results to the client, else
   1434      *        switch to |Single Scan state| and send the result to the client when the scan result
   1435      *        is obtained.
   1436      *
   1437      * Note: PNO scans only work for a single client today. We don't have support in HW to support
   1438      * multiple requests at the same time, so will need non-trivial changes to support (if at all
   1439      * possible) in WifiScanningService.
   1440      */
   1441     class WifiPnoScanStateMachine extends StateMachine implements WifiNative.PnoEventHandler {
   1442 
   1443         private final DefaultState mDefaultState = new DefaultState();
   1444         private final StartedState mStartedState = new StartedState();
   1445         private final HwPnoScanState mHwPnoScanState = new HwPnoScanState();
   1446         private final SingleScanState mSingleScanState = new SingleScanState();
   1447         private InternalClientInfo mInternalClientInfo;
   1448 
   1449         private final RequestList<Pair<PnoSettings, ScanSettings>> mActivePnoScans =
   1450                 new RequestList<>();
   1451 
   1452         WifiPnoScanStateMachine(Looper looper) {
   1453             super("WifiPnoScanStateMachine", looper);
   1454 
   1455             setLogRecSize(256);
   1456             setLogOnlyTransitions(false);
   1457 
   1458             // CHECKSTYLE:OFF IndentationCheck
   1459             addState(mDefaultState);
   1460                 addState(mStartedState, mDefaultState);
   1461                     addState(mHwPnoScanState, mStartedState);
   1462                         addState(mSingleScanState, mHwPnoScanState);
   1463             // CHECKSTYLE:ON IndentationCheck
   1464 
   1465             setInitialState(mDefaultState);
   1466         }
   1467 
   1468         public void removePnoSettings(ClientInfo ci) {
   1469             mActivePnoScans.removeAllForClient(ci);
   1470             transitionTo(mStartedState);
   1471         }
   1472 
   1473         @Override
   1474         public void onPnoNetworkFound(ScanResult[] results) {
   1475             if (DBG) localLog("onWifiPnoNetworkFound event received");
   1476             sendMessage(CMD_PNO_NETWORK_FOUND, 0, 0, results);
   1477         }
   1478 
   1479         @Override
   1480         public void onPnoScanFailed() {
   1481             if (DBG) localLog("onWifiPnoScanFailed event received");
   1482             sendMessage(CMD_PNO_SCAN_FAILED, 0, 0, null);
   1483         }
   1484 
   1485         class DefaultState extends State {
   1486             @Override
   1487             public void enter() {
   1488                 if (DBG) localLog("DefaultState");
   1489             }
   1490 
   1491             @Override
   1492             public boolean processMessage(Message msg) {
   1493                 switch (msg.what) {
   1494                     case CMD_DRIVER_LOADED:
   1495                         if (mScannerImpl == null) {
   1496                             loge("Failed to start pno scan state machine because scanner impl"
   1497                                     + " is null");
   1498                             return HANDLED;
   1499                         }
   1500                         transitionTo(mStartedState);
   1501                         break;
   1502                     case CMD_DRIVER_UNLOADED:
   1503                         transitionTo(mDefaultState);
   1504                         break;
   1505                     case WifiScanner.CMD_START_PNO_SCAN:
   1506                     case WifiScanner.CMD_STOP_PNO_SCAN:
   1507                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
   1508                         break;
   1509                     case CMD_PNO_NETWORK_FOUND:
   1510                     case CMD_PNO_SCAN_FAILED:
   1511                     case WifiScanner.CMD_SCAN_RESULT:
   1512                     case WifiScanner.CMD_OP_FAILED:
   1513                         loge("Unexpected message " + msg.what);
   1514                         break;
   1515                     default:
   1516                         return NOT_HANDLED;
   1517                 }
   1518                 return HANDLED;
   1519             }
   1520         }
   1521 
   1522         class StartedState extends State {
   1523             @Override
   1524             public void enter() {
   1525                 if (DBG) localLog("StartedState");
   1526             }
   1527 
   1528             @Override
   1529             public void exit() {
   1530                 sendPnoScanFailedToAllAndClear(
   1531                         WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted");
   1532             }
   1533 
   1534             @Override
   1535             public boolean processMessage(Message msg) {
   1536                 ClientInfo ci = mClients.get(msg.replyTo);
   1537                 switch (msg.what) {
   1538                     case CMD_DRIVER_LOADED:
   1539                         // Ignore if we're already in driver loaded state.
   1540                         return HANDLED;
   1541                     case WifiScanner.CMD_START_PNO_SCAN:
   1542                         Bundle pnoParams = (Bundle) msg.obj;
   1543                         if (pnoParams == null) {
   1544                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
   1545                             return HANDLED;
   1546                         }
   1547                         pnoParams.setDefusable(true);
   1548                         PnoSettings pnoSettings =
   1549                                 pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
   1550                         if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) {
   1551                             deferMessage(msg);
   1552                             transitionTo(mHwPnoScanState);
   1553                         } else {
   1554                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "not supported");
   1555                         }
   1556                         break;
   1557                     case WifiScanner.CMD_STOP_PNO_SCAN:
   1558                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "no scan running");
   1559                         break;
   1560                     default:
   1561                         return NOT_HANDLED;
   1562                 }
   1563                 return HANDLED;
   1564             }
   1565         }
   1566 
   1567         class HwPnoScanState extends State {
   1568             @Override
   1569             public void enter() {
   1570                 if (DBG) localLog("HwPnoScanState");
   1571             }
   1572 
   1573             @Override
   1574             public void exit() {
   1575                 // Reset PNO scan in ScannerImpl before we exit.
   1576                 mScannerImpl.resetHwPnoList();
   1577                 removeInternalClient();
   1578             }
   1579 
   1580             @Override
   1581             public boolean processMessage(Message msg) {
   1582                 ClientInfo ci = mClients.get(msg.replyTo);
   1583                 switch (msg.what) {
   1584                     case WifiScanner.CMD_START_PNO_SCAN:
   1585                         Bundle pnoParams = (Bundle) msg.obj;
   1586                         if (pnoParams == null) {
   1587                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
   1588                             return HANDLED;
   1589                         }
   1590                         pnoParams.setDefusable(true);
   1591                         PnoSettings pnoSettings =
   1592                                 pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
   1593                         ScanSettings scanSettings =
   1594                                 pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY);
   1595                         if (addHwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
   1596                             replySucceeded(msg);
   1597                         } else {
   1598                             replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
   1599                             transitionTo(mStartedState);
   1600                         }
   1601                         break;
   1602                     case WifiScanner.CMD_STOP_PNO_SCAN:
   1603                         removeHwPnoScanRequest(ci, msg.arg2);
   1604                         transitionTo(mStartedState);
   1605                         break;
   1606                     case CMD_PNO_NETWORK_FOUND:
   1607                         ScanResult[] scanResults = ((ScanResult[]) msg.obj);
   1608                         if (isSingleScanNeeded(scanResults)) {
   1609                             ScanSettings activeScanSettings = getScanSettings();
   1610                             if (activeScanSettings == null) {
   1611                                 sendPnoScanFailedToAllAndClear(
   1612                                         WifiScanner.REASON_UNSPECIFIED,
   1613                                         "couldn't retrieve setting");
   1614                                 transitionTo(mStartedState);
   1615                             } else {
   1616                                 addSingleScanRequest(activeScanSettings);
   1617                                 transitionTo(mSingleScanState);
   1618                             }
   1619                         } else {
   1620                             reportPnoNetworkFound((ScanResult[]) msg.obj);
   1621                         }
   1622                         break;
   1623                     case CMD_PNO_SCAN_FAILED:
   1624                         sendPnoScanFailedToAllAndClear(
   1625                                 WifiScanner.REASON_UNSPECIFIED, "pno scan failed");
   1626                         transitionTo(mStartedState);
   1627                         break;
   1628                     default:
   1629                         return NOT_HANDLED;
   1630                 }
   1631                 return HANDLED;
   1632             }
   1633         }
   1634 
   1635         class SingleScanState extends State {
   1636             @Override
   1637             public void enter() {
   1638                 if (DBG) localLog("SingleScanState");
   1639             }
   1640 
   1641             @Override
   1642             public boolean processMessage(Message msg) {
   1643                 ClientInfo ci = mClients.get(msg.replyTo);
   1644                 switch (msg.what) {
   1645                     case WifiScanner.CMD_SCAN_RESULT:
   1646                         WifiScanner.ParcelableScanData parcelableScanData =
   1647                                 (WifiScanner.ParcelableScanData) msg.obj;
   1648                         ScanData[] scanDatas = parcelableScanData.getResults();
   1649                         ScanData lastScanData = scanDatas[scanDatas.length - 1];
   1650                         reportPnoNetworkFound(lastScanData.getResults());
   1651                         transitionTo(mHwPnoScanState);
   1652                         break;
   1653                     case WifiScanner.CMD_OP_FAILED:
   1654                         sendPnoScanFailedToAllAndClear(
   1655                                 WifiScanner.REASON_UNSPECIFIED, "single scan failed");
   1656                         transitionTo(mStartedState);
   1657                         break;
   1658                     default:
   1659                         return NOT_HANDLED;
   1660                 }
   1661                 return HANDLED;
   1662             }
   1663         }
   1664 
   1665         private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings,
   1666                                                                   PnoSettings pnoSettings) {
   1667             WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings();
   1668             nativePnoSetting.periodInMs = scanSettings.periodInMs;
   1669             nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi;
   1670             nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi;
   1671             nativePnoSetting.initialScoreMax = pnoSettings.initialScoreMax;
   1672             nativePnoSetting.currentConnectionBonus = pnoSettings.currentConnectionBonus;
   1673             nativePnoSetting.sameNetworkBonus = pnoSettings.sameNetworkBonus;
   1674             nativePnoSetting.secureBonus = pnoSettings.secureBonus;
   1675             nativePnoSetting.band5GHzBonus = pnoSettings.band5GHzBonus;
   1676             nativePnoSetting.isConnected = pnoSettings.isConnected;
   1677             nativePnoSetting.networkList =
   1678                     new WifiNative.PnoNetwork[pnoSettings.networkList.length];
   1679             for (int i = 0; i < pnoSettings.networkList.length; i++) {
   1680                 nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork();
   1681                 nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid;
   1682                 nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags;
   1683                 nativePnoSetting.networkList[i].auth_bit_field =
   1684                         pnoSettings.networkList[i].authBitField;
   1685             }
   1686             return nativePnoSetting;
   1687         }
   1688 
   1689         // Retrieve the only active scan settings.
   1690         private ScanSettings getScanSettings() {
   1691             for (Pair<PnoSettings, ScanSettings> settingsPair : mActivePnoScans.getAllSettings()) {
   1692                 return settingsPair.second;
   1693             }
   1694             return null;
   1695         }
   1696 
   1697         private void removeInternalClient() {
   1698             if (mInternalClientInfo != null) {
   1699                 mInternalClientInfo.cleanup();
   1700                 mInternalClientInfo = null;
   1701             } else {
   1702                 Log.w(TAG, "No Internal client for PNO");
   1703             }
   1704         }
   1705 
   1706         private void addInternalClient(ClientInfo ci) {
   1707             if (mInternalClientInfo == null) {
   1708                 mInternalClientInfo =
   1709                         new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler()));
   1710                 mInternalClientInfo.register();
   1711             } else {
   1712                 Log.w(TAG, "Internal client for PNO already exists");
   1713             }
   1714         }
   1715 
   1716         private void addPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
   1717                 PnoSettings pnoSettings) {
   1718             mActivePnoScans.addRequest(ci, handler, WifiStateMachine.WIFI_WORK_SOURCE,
   1719                     Pair.create(pnoSettings, scanSettings));
   1720             addInternalClient(ci);
   1721         }
   1722 
   1723         private Pair<PnoSettings, ScanSettings> removePnoScanRequest(ClientInfo ci, int handler) {
   1724             Pair<PnoSettings, ScanSettings> settings = mActivePnoScans.removeRequest(ci, handler);
   1725             return settings;
   1726         }
   1727 
   1728         private boolean addHwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
   1729                 PnoSettings pnoSettings) {
   1730             if (ci == null) {
   1731                 Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
   1732                 return false;
   1733             }
   1734             if (!mActivePnoScans.isEmpty()) {
   1735                 loge("Failing scan request because there is already an active scan");
   1736                 return false;
   1737             }
   1738             WifiNative.PnoSettings nativePnoSettings =
   1739                     convertSettingsToPnoNative(scanSettings, pnoSettings);
   1740             if (!mScannerImpl.setHwPnoList(nativePnoSettings, mPnoScanStateMachine)) {
   1741                 return false;
   1742             }
   1743             logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
   1744             addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
   1745 
   1746             return true;
   1747         }
   1748 
   1749         private void removeHwPnoScanRequest(ClientInfo ci, int handler) {
   1750             if (ci != null) {
   1751                 Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci, handler);
   1752                 logScanRequest("removeHwPnoScanRequest", ci, handler, null,
   1753                         settings.second, settings.first);
   1754             }
   1755         }
   1756 
   1757         private void reportPnoNetworkFound(ScanResult[] results) {
   1758             WifiScanner.ParcelableScanResults parcelableScanResults =
   1759                     new WifiScanner.ParcelableScanResults(results);
   1760             for (RequestInfo<Pair<PnoSettings, ScanSettings>> entry : mActivePnoScans) {
   1761                 ClientInfo ci = entry.clientInfo;
   1762                 int handler = entry.handlerId;
   1763                 logCallback("pnoNetworkFound", ci, handler, describeForLog(results));
   1764                 ci.reportEvent(
   1765                         WifiScanner.CMD_PNO_NETWORK_FOUND, 0, handler, parcelableScanResults);
   1766             }
   1767         }
   1768 
   1769         private void sendPnoScanFailedToAllAndClear(int reason, String description) {
   1770             for (RequestInfo<Pair<PnoSettings, ScanSettings>> entry : mActivePnoScans) {
   1771                 ClientInfo ci = entry.clientInfo;
   1772                 int handler = entry.handlerId;
   1773                 ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler,
   1774                         new WifiScanner.OperationResult(reason, description));
   1775             }
   1776             mActivePnoScans.clear();
   1777         }
   1778 
   1779         private void addSingleScanRequest(ScanSettings settings) {
   1780             if (DBG) localLog("Starting single scan");
   1781             if (mInternalClientInfo != null) {
   1782                 mInternalClientInfo.sendRequestToClientHandler(
   1783                         WifiScanner.CMD_START_SINGLE_SCAN, settings,
   1784                         WifiStateMachine.WIFI_WORK_SOURCE);
   1785             }
   1786         }
   1787 
   1788         /**
   1789          * Checks if IE are present in scan data, if no single scan is needed to report event to
   1790          * client
   1791          */
   1792         private boolean isSingleScanNeeded(ScanResult[] scanResults) {
   1793             for (ScanResult scanResult : scanResults) {
   1794                 if (scanResult.informationElements != null
   1795                         && scanResult.informationElements.length > 0) {
   1796                     return false;
   1797                 }
   1798             }
   1799             return true;
   1800         }
   1801     }
   1802 
   1803     private abstract class ClientInfo {
   1804         private final int mUid;
   1805         private final WorkSource mWorkSource;
   1806         private boolean mScanWorkReported = false;
   1807         protected final Messenger mMessenger;
   1808 
   1809         ClientInfo(int uid, Messenger messenger) {
   1810             mUid = uid;
   1811             mMessenger = messenger;
   1812             mWorkSource = new WorkSource(uid);
   1813         }
   1814 
   1815         /**
   1816          * Register this client to main client map.
   1817          */
   1818         public void register() {
   1819             mClients.put(mMessenger, this);
   1820         }
   1821 
   1822         /**
   1823          * Unregister this client from main client map.
   1824          */
   1825         private void unregister() {
   1826             mClients.remove(mMessenger);
   1827         }
   1828 
   1829         public void cleanup() {
   1830             mSingleScanListeners.removeAllForClient(this);
   1831             mSingleScanStateMachine.removeSingleScanRequests(this);
   1832             mBackgroundScanStateMachine.removeBackgroundScanSettings(this);
   1833             unregister();
   1834             localLog("Successfully stopped all requests for client " + this);
   1835         }
   1836 
   1837         public int getUid() {
   1838             return mUid;
   1839         }
   1840 
   1841         public void reportEvent(int what, int arg1, int arg2) {
   1842             reportEvent(what, arg1, arg2, null);
   1843         }
   1844 
   1845         // This has to be implemented by subclasses to report events back to clients.
   1846         public abstract void reportEvent(int what, int arg1, int arg2, Object obj);
   1847 
   1848         // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ?
   1849         private void reportBatchedScanStart() {
   1850             if (mUid == 0)
   1851                 return;
   1852 
   1853             int csph = getCsph();
   1854 
   1855             try {
   1856                 mBatteryStats.noteWifiBatchedScanStartedFromSource(mWorkSource, csph);
   1857             } catch (RemoteException e) {
   1858                 logw("failed to report scan work: " + e.toString());
   1859             }
   1860         }
   1861 
   1862         // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ?
   1863         private void reportBatchedScanStop() {
   1864             if (mUid == 0)
   1865                 return;
   1866 
   1867             try {
   1868                 mBatteryStats.noteWifiBatchedScanStoppedFromSource(mWorkSource);
   1869             } catch (RemoteException e) {
   1870                 logw("failed to cleanup scan work: " + e.toString());
   1871             }
   1872         }
   1873 
   1874         // TODO migrate batterystats to accept scan duration per hour instead of csph
   1875         private int getCsph() {
   1876             int totalScanDurationPerHour = 0;
   1877             Collection<ScanSettings> settingsList =
   1878                     mBackgroundScanStateMachine.getBackgroundScanSettings(this);
   1879             for (ScanSettings settings : settingsList) {
   1880                 int scanDurationMs = mChannelHelper.estimateScanDuration(settings);
   1881                 int scans_per_Hour = settings.periodInMs == 0 ? 1 : (3600 * 1000) /
   1882                         settings.periodInMs;
   1883                 totalScanDurationPerHour += scanDurationMs * scans_per_Hour;
   1884             }
   1885 
   1886             return totalScanDurationPerHour / ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS;
   1887         }
   1888 
   1889         // TODO(b/27903217, 71530998): This is dead code. Should this be wired up ?
   1890         private void reportScanWorkUpdate() {
   1891             if (mScanWorkReported) {
   1892                 reportBatchedScanStop();
   1893                 mScanWorkReported = false;
   1894             }
   1895             if (mBackgroundScanStateMachine.getBackgroundScanSettings(this).isEmpty()) {
   1896                 reportBatchedScanStart();
   1897                 mScanWorkReported = true;
   1898             }
   1899         }
   1900 
   1901         @Override
   1902         public String toString() {
   1903             return "ClientInfo[uid=" + mUid + "," + mMessenger + "]";
   1904         }
   1905     }
   1906 
   1907     /**
   1908      * This class is used to represent external clients to the WifiScanning Service.
   1909      */
   1910     private class ExternalClientInfo extends ClientInfo {
   1911         private final AsyncChannel mChannel;
   1912         /**
   1913          * Indicates if the client is still connected
   1914          * If the client is no longer connected then messages to it will be silently dropped
   1915          */
   1916         private boolean mDisconnected = false;
   1917 
   1918         ExternalClientInfo(int uid, Messenger messenger, AsyncChannel c) {
   1919             super(uid, messenger);
   1920             mChannel = c;
   1921             if (DBG) localLog("New client, channel: " + c);
   1922         }
   1923 
   1924         @Override
   1925         public void reportEvent(int what, int arg1, int arg2, Object obj) {
   1926             if (!mDisconnected) {
   1927                 mChannel.sendMessage(what, arg1, arg2, obj);
   1928             }
   1929         }
   1930 
   1931         @Override
   1932         public void cleanup() {
   1933             mDisconnected = true;
   1934             mPnoScanStateMachine.removePnoSettings(this);
   1935             super.cleanup();
   1936         }
   1937     }
   1938 
   1939     /**
   1940      * This class is used to represent internal clients to the WifiScanning Service. This is needed
   1941      * for communicating between State Machines.
   1942      * This leaves the onReportEvent method unimplemented, so that the clients have the freedom
   1943      * to handle the events as they need.
   1944      */
   1945     private class InternalClientInfo extends ClientInfo {
   1946         private static final int INTERNAL_CLIENT_HANDLER = 0;
   1947 
   1948         /**
   1949          * The UID here is used to proxy the original external requester UID.
   1950          */
   1951         InternalClientInfo(int requesterUid, Messenger messenger) {
   1952             super(requesterUid, messenger);
   1953         }
   1954 
   1955         @Override
   1956         public void reportEvent(int what, int arg1, int arg2, Object obj) {
   1957             Message message = Message.obtain();
   1958             message.what = what;
   1959             message.arg1 = arg1;
   1960             message.arg2 = arg2;
   1961             message.obj = obj;
   1962             try {
   1963                 mMessenger.send(message);
   1964             } catch (RemoteException e) {
   1965                 loge("Failed to send message: " + what);
   1966             }
   1967         }
   1968 
   1969         /**
   1970          * Send a message to the client handler which should reroute the message to the appropriate
   1971          * state machine.
   1972          */
   1973         public void sendRequestToClientHandler(int what, ScanSettings settings,
   1974                 WorkSource workSource) {
   1975             Message msg = Message.obtain();
   1976             msg.what = what;
   1977             msg.arg2 = INTERNAL_CLIENT_HANDLER;
   1978             if (settings != null) {
   1979                 Bundle bundle = new Bundle();
   1980                 bundle.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
   1981                 bundle.putParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
   1982                 msg.obj = bundle;
   1983             }
   1984             msg.replyTo = mMessenger;
   1985             msg.sendingUid = getUid();
   1986             mClientHandler.sendMessage(msg);
   1987         }
   1988 
   1989         /**
   1990          * Send a message to the client handler which should reroute the message to the appropriate
   1991          * state machine.
   1992          */
   1993         public void sendRequestToClientHandler(int what) {
   1994             sendRequestToClientHandler(what, null, null);
   1995         }
   1996 
   1997         @Override
   1998         public String toString() {
   1999             return "InternalClientInfo[]";
   2000         }
   2001     }
   2002 
   2003     void replySucceeded(Message msg) {
   2004         if (msg.replyTo != null) {
   2005             Message reply = Message.obtain();
   2006             reply.what = WifiScanner.CMD_OP_SUCCEEDED;
   2007             reply.arg2 = msg.arg2;
   2008             if (msg.obj != null) {
   2009                 reply.obj = msg.obj;
   2010             }
   2011             try {
   2012                 msg.replyTo.send(reply);
   2013                 mLog.trace("replySucceeded recvdMessage=%").c(msg.what).flush();
   2014             } catch (RemoteException e) {
   2015                 // There's not much we can do if reply can't be sent!
   2016             }
   2017         } else {
   2018             // locally generated message; doesn't need a reply!
   2019         }
   2020     }
   2021 
   2022     void replyFailed(Message msg, int reason, String description) {
   2023         if (msg.replyTo != null) {
   2024             Message reply = Message.obtain();
   2025             reply.what = WifiScanner.CMD_OP_FAILED;
   2026             reply.arg2 = msg.arg2;
   2027             reply.obj = new WifiScanner.OperationResult(reason, description);
   2028             try {
   2029                 msg.replyTo.send(reply);
   2030                 mLog.trace("replyFailed recvdMessage=% reason=%")
   2031                             .c(msg.what)
   2032                             .c(reason)
   2033                             .flush();
   2034             } catch (RemoteException e) {
   2035                 // There's not much we can do if reply can't be sent!
   2036             }
   2037         } else {
   2038             // locally generated message; doesn't need a reply!
   2039         }
   2040     }
   2041 
   2042     private static String toString(int uid, ScanSettings settings) {
   2043         StringBuilder sb = new StringBuilder();
   2044         sb.append("ScanSettings[uid=").append(uid);
   2045         sb.append(", period=").append(settings.periodInMs);
   2046         sb.append(", report=").append(settings.reportEvents);
   2047         if (settings.reportEvents == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
   2048                 && settings.numBssidsPerScan > 0
   2049                 && settings.maxScansToCache > 1) {
   2050             sb.append(", batch=").append(settings.maxScansToCache);
   2051             sb.append(", numAP=").append(settings.numBssidsPerScan);
   2052         }
   2053         sb.append(", ").append(ChannelHelper.toString(settings));
   2054         sb.append("]");
   2055 
   2056         return sb.toString();
   2057     }
   2058 
   2059     @Override
   2060     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   2061         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   2062                 != PERMISSION_GRANTED) {
   2063             pw.println("Permission Denial: can't dump WifiScanner from from pid="
   2064                     + Binder.getCallingPid()
   2065                     + ", uid=" + Binder.getCallingUid()
   2066                     + " without permission "
   2067                     + android.Manifest.permission.DUMP);
   2068             return;
   2069         }
   2070         pw.println("WifiScanningService - Log Begin ----");
   2071         mLocalLog.dump(fd, pw, args);
   2072         pw.println("WifiScanningService - Log End ----");
   2073         pw.println();
   2074         pw.println("clients:");
   2075         for (ClientInfo client : mClients.values()) {
   2076             pw.println("  " + client);
   2077         }
   2078         pw.println("listeners:");
   2079         for (ClientInfo client : mClients.values()) {
   2080             Collection<ScanSettings> settingsList =
   2081                     mBackgroundScanStateMachine.getBackgroundScanSettings(client);
   2082             for (ScanSettings settings : settingsList) {
   2083                 pw.println("  " + toString(client.mUid, settings));
   2084             }
   2085         }
   2086         if (mBackgroundScheduler != null) {
   2087             WifiNative.ScanSettings schedule = mBackgroundScheduler.getSchedule();
   2088             if (schedule != null) {
   2089                 pw.println("schedule:");
   2090                 pw.println("  base period: " + schedule.base_period_ms);
   2091                 pw.println("  max ap per scan: " + schedule.max_ap_per_scan);
   2092                 pw.println("  batched scans: " + schedule.report_threshold_num_scans);
   2093                 pw.println("  buckets:");
   2094                 for (int b = 0; b < schedule.num_buckets; b++) {
   2095                     WifiNative.BucketSettings bucket = schedule.buckets[b];
   2096                     pw.println("    bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)["
   2097                             + bucket.report_events + "]: "
   2098                             + ChannelHelper.toString(bucket));
   2099                 }
   2100             }
   2101         }
   2102         if (mPnoScanStateMachine != null) {
   2103             mPnoScanStateMachine.dump(fd, pw, args);
   2104         }
   2105         pw.println();
   2106 
   2107         if (mSingleScanStateMachine != null) {
   2108             mSingleScanStateMachine.dump(fd, pw, args);
   2109             pw.println();
   2110             pw.println("Latest scan results:");
   2111             List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList();
   2112             long nowMs = mClock.getElapsedSinceBootMillis();
   2113             ScanResultUtil.dumpScanResults(pw, scanResults, nowMs);
   2114             pw.println();
   2115         }
   2116         if (mScannerImpl != null) {
   2117             mScannerImpl.dump(fd, pw, args);
   2118         }
   2119     }
   2120 
   2121     void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource,
   2122             ScanSettings settings, PnoSettings pnoSettings) {
   2123         StringBuilder sb = new StringBuilder();
   2124         sb.append(request)
   2125                 .append(": ")
   2126                 .append((ci == null) ? "ClientInfo[unknown]" : ci.toString())
   2127                 .append(",Id=")
   2128                 .append(id);
   2129         if (workSource != null) {
   2130             sb.append(",").append(workSource);
   2131         }
   2132         if (settings != null) {
   2133             sb.append(", ");
   2134             describeTo(sb, settings);
   2135         }
   2136         if (pnoSettings != null) {
   2137             sb.append(", ");
   2138             describeTo(sb, pnoSettings);
   2139         }
   2140         localLog(sb.toString());
   2141     }
   2142 
   2143     void logCallback(String callback, ClientInfo ci, int id, String extra) {
   2144         StringBuilder sb = new StringBuilder();
   2145         sb.append(callback)
   2146                 .append(": ")
   2147                 .append((ci == null) ? "ClientInfo[unknown]" : ci.toString())
   2148                 .append(",Id=")
   2149                 .append(id);
   2150         if (extra != null) {
   2151             sb.append(",").append(extra);
   2152         }
   2153         localLog(sb.toString());
   2154     }
   2155 
   2156     static String describeForLog(ScanData[] results) {
   2157         StringBuilder sb = new StringBuilder();
   2158         sb.append("results=");
   2159         for (int i = 0; i < results.length; ++i) {
   2160             if (i > 0) sb.append(";");
   2161             sb.append(results[i].getResults().length);
   2162         }
   2163         return sb.toString();
   2164     }
   2165 
   2166     static String describeForLog(ScanResult[] results) {
   2167         return "results=" + results.length;
   2168     }
   2169 
   2170     static String getScanTypeString(int type) {
   2171         switch(type) {
   2172             case WifiScanner.TYPE_LOW_LATENCY:
   2173                 return "LOW LATENCY";
   2174             case WifiScanner.TYPE_LOW_POWER:
   2175                 return "LOW POWER";
   2176             case WifiScanner.TYPE_HIGH_ACCURACY:
   2177                 return "HIGH ACCURACY";
   2178             default:
   2179                 // This should never happen becuase we've validated the incoming type in
   2180                 // |validateScanType|.
   2181                 throw new IllegalArgumentException("Invalid scan type " + type);
   2182         }
   2183     }
   2184 
   2185     static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
   2186         sb.append("ScanSettings { ")
   2187           .append(" type:").append(getScanTypeString(scanSettings.type))
   2188           .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
   2189           .append(" period:").append(scanSettings.periodInMs)
   2190           .append(" reportEvents:").append(scanSettings.reportEvents)
   2191           .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
   2192           .append(" maxScansToCache:").append(scanSettings.maxScansToCache)
   2193           .append(" channels:[ ");
   2194         if (scanSettings.channels != null) {
   2195             for (int i = 0; i < scanSettings.channels.length; i++) {
   2196                 sb.append(scanSettings.channels[i].frequency)
   2197                   .append(" ");
   2198             }
   2199         }
   2200         sb.append(" ] ")
   2201           .append(" } ");
   2202         return sb.toString();
   2203     }
   2204 
   2205     static String describeTo(StringBuilder sb, PnoSettings pnoSettings) {
   2206         sb.append("PnoSettings { ")
   2207           .append(" min5GhzRssi:").append(pnoSettings.min5GHzRssi)
   2208           .append(" min24GhzRssi:").append(pnoSettings.min24GHzRssi)
   2209           .append(" initialScoreMax:").append(pnoSettings.initialScoreMax)
   2210           .append(" currentConnectionBonus:").append(pnoSettings.currentConnectionBonus)
   2211           .append(" sameNetworkBonus:").append(pnoSettings.sameNetworkBonus)
   2212           .append(" secureBonus:").append(pnoSettings.secureBonus)
   2213           .append(" band5GhzBonus:").append(pnoSettings.band5GHzBonus)
   2214           .append(" isConnected:").append(pnoSettings.isConnected)
   2215           .append(" networks:[ ");
   2216         if (pnoSettings.networkList != null) {
   2217             for (int i = 0; i < pnoSettings.networkList.length; i++) {
   2218                 sb.append(pnoSettings.networkList[i].ssid).append(",");
   2219             }
   2220         }
   2221         sb.append(" ] ")
   2222           .append(" } ");
   2223         return sb.toString();
   2224     }
   2225 }
   2226