Home | History | Annotate | Download | only in wifi
      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 android.net.wifi;
     18 
     19 import android.annotation.SystemApi;
     20 import android.content.Context;
     21 import android.os.Bundle;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.os.Messenger;
     26 import android.os.Parcel;
     27 import android.os.Parcelable;
     28 import android.os.RemoteException;
     29 import android.os.WorkSource;
     30 import android.util.Log;
     31 import android.util.SparseArray;
     32 
     33 import com.android.internal.util.AsyncChannel;
     34 import com.android.internal.util.Preconditions;
     35 import com.android.internal.util.Protocol;
     36 
     37 import java.util.List;
     38 
     39 
     40 /**
     41  * This class provides a way to scan the Wifi universe around the device
     42  * Get an instance of this class by calling
     43  * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
     44  * .WIFI_SCANNING_SERVICE)}.
     45  * @hide
     46  */
     47 @SystemApi
     48 public class WifiScanner {
     49 
     50     /** no band specified; use channel list instead */
     51     public static final int WIFI_BAND_UNSPECIFIED = 0;      /* not specified */
     52 
     53     /** 2.4 GHz band */
     54     public static final int WIFI_BAND_24_GHZ = 1;           /* 2.4 GHz band */
     55     /** 5 GHz band excluding DFS channels */
     56     public static final int WIFI_BAND_5_GHZ = 2;            /* 5 GHz band without DFS channels */
     57     /** DFS channels from 5 GHz band only */
     58     public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 4;  /* 5 GHz band with DFS channels */
     59     /** 5 GHz band including DFS channels */
     60     public static final int WIFI_BAND_5_GHZ_WITH_DFS  = 6;  /* 5 GHz band with DFS channels */
     61     /** Both 2.4 GHz band and 5 GHz band; no DFS channels */
     62     public static final int WIFI_BAND_BOTH = 3;             /* both bands without DFS channels */
     63     /** Both 2.4 GHz band and 5 GHz band; with DFS channels */
     64     public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
     65 
     66     /** Minimum supported scanning period */
     67     public static final int MIN_SCAN_PERIOD_MS = 1000;      /* minimum supported period */
     68     /** Maximum supported scanning period */
     69     public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
     70 
     71     /** No Error */
     72     public static final int REASON_SUCCEEDED = 0;
     73     /** Unknown error */
     74     public static final int REASON_UNSPECIFIED = -1;
     75     /** Invalid listener */
     76     public static final int REASON_INVALID_LISTENER = -2;
     77     /** Invalid request */
     78     public static final int REASON_INVALID_REQUEST = -3;
     79     /** Invalid request */
     80     public static final int REASON_NOT_AUTHORIZED = -4;
     81     /** An outstanding request with the same listener hasn't finished yet. */
     82     public static final int REASON_DUPLICATE_REQEUST = -5;
     83 
     84     /** @hide */
     85     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
     86 
     87     /**
     88      * Generic action callback invocation interface
     89      *  @hide
     90      */
     91     @SystemApi
     92     public static interface ActionListener {
     93         public void onSuccess();
     94         public void onFailure(int reason, String description);
     95     }
     96 
     97     /**
     98      * gives you all the possible channels; channel is specified as an
     99      * integer with frequency in MHz i.e. channel 1 is 2412
    100      * @hide
    101      */
    102     public List<Integer> getAvailableChannels(int band) {
    103         try {
    104             Bundle bundle =  mService.getAvailableChannels(band);
    105             return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
    106         } catch (RemoteException e) {
    107             return null;
    108         }
    109     }
    110 
    111     /**
    112      * provides channel specification for scanning
    113      */
    114     public static class ChannelSpec {
    115         /**
    116          * channel frequency in MHz; for example channel 1 is specified as 2412
    117          */
    118         public int frequency;
    119         /**
    120          * if true, scan this channel in passive fashion.
    121          * This flag is ignored on DFS channel specification.
    122          * @hide
    123          */
    124         public boolean passive;                                    /* ignored on DFS channels */
    125         /**
    126          * how long to dwell on this channel
    127          * @hide
    128          */
    129         public int dwellTimeMS;                                    /* not supported for now */
    130 
    131         /**
    132          * default constructor for channel spec
    133          */
    134         public ChannelSpec(int frequency) {
    135             this.frequency = frequency;
    136             passive = false;
    137             dwellTimeMS = 0;
    138         }
    139     }
    140 
    141     /**
    142      * reports {@link ScanListener#onResults} when underlying buffers are full
    143      * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag
    144      * @deprecated It is not supported anymore.
    145      */
    146     @Deprecated
    147     public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
    148     /**
    149      * reports {@link ScanListener#onResults} after each scan
    150      */
    151     public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0);
    152     /**
    153      * reports {@link ScanListener#onFullResult} whenever each beacon is discovered
    154      */
    155     public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1);
    156     /**
    157      * Do not place scans in the chip's scan history buffer
    158      */
    159     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
    160 
    161 
    162     /** {@hide} */
    163     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
    164     /** {@hide} */
    165     public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
    166     /**
    167      * scan configuration parameters to be sent to {@link #startBackgroundScan}
    168      */
    169     public static class ScanSettings implements Parcelable {
    170 
    171         /** one of the WIFI_BAND values */
    172         public int band;
    173         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
    174         public ChannelSpec[] channels;
    175         /**
    176          * list of networkId's of hidden networks to scan for.
    177          * These Id's should correspond to the wpa_supplicant's networkId's and will be used
    178          * in connectivity scans using wpa_supplicant.
    179          * {@hide}
    180          * */
    181         public int[] hiddenNetworkIds;
    182         /** period of background scan; in millisecond, 0 => single shot scan */
    183         public int periodInMs;
    184         /** must have a valid REPORT_EVENT value */
    185         public int reportEvents;
    186         /** defines number of bssids to cache from each scan */
    187         public int numBssidsPerScan;
    188         /**
    189          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
    190          * to wake up at fixed interval
    191          */
    192         public int maxScansToCache;
    193         /**
    194          * if maxPeriodInMs is non zero or different than period, then this bucket is
    195          * a truncated binary exponential backoff bucket and the scan period will grow
    196          * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
    197          * to maxPeriodInMs
    198          */
    199         public int maxPeriodInMs;
    200         /**
    201          * for truncated binary exponential back off bucket, number of scans to perform
    202          * for a given period
    203          */
    204         public int stepCount;
    205         /**
    206          * Flag to indicate if the scan settings are targeted for PNO scan.
    207          * {@hide}
    208          */
    209         public boolean isPnoScan;
    210 
    211         /** Implement the Parcelable interface {@hide} */
    212         public int describeContents() {
    213             return 0;
    214         }
    215 
    216         /** Implement the Parcelable interface {@hide} */
    217         public void writeToParcel(Parcel dest, int flags) {
    218             dest.writeInt(band);
    219             dest.writeInt(periodInMs);
    220             dest.writeInt(reportEvents);
    221             dest.writeInt(numBssidsPerScan);
    222             dest.writeInt(maxScansToCache);
    223             dest.writeInt(maxPeriodInMs);
    224             dest.writeInt(stepCount);
    225             dest.writeInt(isPnoScan ? 1 : 0);
    226             if (channels != null) {
    227                 dest.writeInt(channels.length);
    228                 for (int i = 0; i < channels.length; i++) {
    229                     dest.writeInt(channels[i].frequency);
    230                     dest.writeInt(channels[i].dwellTimeMS);
    231                     dest.writeInt(channels[i].passive ? 1 : 0);
    232                 }
    233             } else {
    234                 dest.writeInt(0);
    235             }
    236             dest.writeIntArray(hiddenNetworkIds);
    237         }
    238 
    239         /** Implement the Parcelable interface {@hide} */
    240         public static final Creator<ScanSettings> CREATOR =
    241                 new Creator<ScanSettings>() {
    242                     public ScanSettings createFromParcel(Parcel in) {
    243                         ScanSettings settings = new ScanSettings();
    244                         settings.band = in.readInt();
    245                         settings.periodInMs = in.readInt();
    246                         settings.reportEvents = in.readInt();
    247                         settings.numBssidsPerScan = in.readInt();
    248                         settings.maxScansToCache = in.readInt();
    249                         settings.maxPeriodInMs = in.readInt();
    250                         settings.stepCount = in.readInt();
    251                         settings.isPnoScan = in.readInt() == 1;
    252                         int num_channels = in.readInt();
    253                         settings.channels = new ChannelSpec[num_channels];
    254                         for (int i = 0; i < num_channels; i++) {
    255                             int frequency = in.readInt();
    256                             ChannelSpec spec = new ChannelSpec(frequency);
    257                             spec.dwellTimeMS = in.readInt();
    258                             spec.passive = in.readInt() == 1;
    259                             settings.channels[i] = spec;
    260                         }
    261                         settings.hiddenNetworkIds = in.createIntArray();
    262                         return settings;
    263                     }
    264 
    265                     public ScanSettings[] newArray(int size) {
    266                         return new ScanSettings[size];
    267                     }
    268                 };
    269 
    270     }
    271 
    272     /**
    273      * all the information garnered from a single scan
    274      */
    275     public static class ScanData implements Parcelable {
    276         /** scan identifier */
    277         private int mId;
    278         /** additional information about scan
    279          * 0 => no special issues encountered in the scan
    280          * non-zero => scan was truncated, so results may not be complete
    281          */
    282         private int mFlags;
    283         /**
    284          * Indicates the buckets that were scanned to generate these results.
    285          * This is not relevant to WifiScanner API users and is used internally.
    286          * {@hide}
    287          */
    288         private int mBucketsScanned;
    289         /**
    290          * Indicates that the scan results received are as a result of a scan of all available
    291          * channels. This should only be expected to function for single scans.
    292          * {@hide}
    293          */
    294         private boolean mAllChannelsScanned;
    295         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
    296         private ScanResult mResults[];
    297 
    298         ScanData() {}
    299 
    300         public ScanData(int id, int flags, ScanResult[] results) {
    301             mId = id;
    302             mFlags = flags;
    303             mResults = results;
    304         }
    305 
    306         /** {@hide} */
    307         public ScanData(int id, int flags, int bucketsScanned, boolean allChannelsScanned,
    308                 ScanResult[] results) {
    309             mId = id;
    310             mFlags = flags;
    311             mBucketsScanned = bucketsScanned;
    312             mAllChannelsScanned = allChannelsScanned;
    313             mResults = results;
    314         }
    315 
    316         public ScanData(ScanData s) {
    317             mId = s.mId;
    318             mFlags = s.mFlags;
    319             mBucketsScanned = s.mBucketsScanned;
    320             mAllChannelsScanned = s.mAllChannelsScanned;
    321             mResults = new ScanResult[s.mResults.length];
    322             for (int i = 0; i < s.mResults.length; i++) {
    323                 ScanResult result = s.mResults[i];
    324                 ScanResult newResult = new ScanResult(result);
    325                 mResults[i] = newResult;
    326             }
    327         }
    328 
    329         public int getId() {
    330             return mId;
    331         }
    332 
    333         public int getFlags() {
    334             return mFlags;
    335         }
    336 
    337         /** {@hide} */
    338         public int getBucketsScanned() {
    339             return mBucketsScanned;
    340         }
    341 
    342         /** {@hide} */
    343         public boolean isAllChannelsScanned() {
    344             return mAllChannelsScanned;
    345         }
    346 
    347         public ScanResult[] getResults() {
    348             return mResults;
    349         }
    350 
    351         /** Implement the Parcelable interface {@hide} */
    352         public int describeContents() {
    353             return 0;
    354         }
    355 
    356         /** Implement the Parcelable interface {@hide} */
    357         public void writeToParcel(Parcel dest, int flags) {
    358             if (mResults != null) {
    359                 dest.writeInt(mId);
    360                 dest.writeInt(mFlags);
    361                 dest.writeInt(mBucketsScanned);
    362                 dest.writeInt(mAllChannelsScanned ? 1 : 0);
    363                 dest.writeInt(mResults.length);
    364                 for (int i = 0; i < mResults.length; i++) {
    365                     ScanResult result = mResults[i];
    366                     result.writeToParcel(dest, flags);
    367                 }
    368             } else {
    369                 dest.writeInt(0);
    370             }
    371         }
    372 
    373         /** Implement the Parcelable interface {@hide} */
    374         public static final Creator<ScanData> CREATOR =
    375                 new Creator<ScanData>() {
    376                     public ScanData createFromParcel(Parcel in) {
    377                         int id = in.readInt();
    378                         int flags = in.readInt();
    379                         int bucketsScanned = in.readInt();
    380                         boolean allChannelsScanned = in.readInt() != 0;
    381                         int n = in.readInt();
    382                         ScanResult results[] = new ScanResult[n];
    383                         for (int i = 0; i < n; i++) {
    384                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    385                         }
    386                         return new ScanData(id, flags, bucketsScanned, allChannelsScanned, results);
    387                     }
    388 
    389                     public ScanData[] newArray(int size) {
    390                         return new ScanData[size];
    391                     }
    392                 };
    393     }
    394 
    395     public static class ParcelableScanData implements Parcelable {
    396 
    397         public ScanData mResults[];
    398 
    399         public ParcelableScanData(ScanData[] results) {
    400             mResults = results;
    401         }
    402 
    403         public ScanData[] getResults() {
    404             return mResults;
    405         }
    406 
    407         /** Implement the Parcelable interface {@hide} */
    408         public int describeContents() {
    409             return 0;
    410         }
    411 
    412         /** Implement the Parcelable interface {@hide} */
    413         public void writeToParcel(Parcel dest, int flags) {
    414             if (mResults != null) {
    415                 dest.writeInt(mResults.length);
    416                 for (int i = 0; i < mResults.length; i++) {
    417                     ScanData result = mResults[i];
    418                     result.writeToParcel(dest, flags);
    419                 }
    420             } else {
    421                 dest.writeInt(0);
    422             }
    423         }
    424 
    425         /** Implement the Parcelable interface {@hide} */
    426         public static final Creator<ParcelableScanData> CREATOR =
    427                 new Creator<ParcelableScanData>() {
    428                     public ParcelableScanData createFromParcel(Parcel in) {
    429                         int n = in.readInt();
    430                         ScanData results[] = new ScanData[n];
    431                         for (int i = 0; i < n; i++) {
    432                             results[i] = ScanData.CREATOR.createFromParcel(in);
    433                         }
    434                         return new ParcelableScanData(results);
    435                     }
    436 
    437                     public ParcelableScanData[] newArray(int size) {
    438                         return new ParcelableScanData[size];
    439                     }
    440                 };
    441     }
    442 
    443     public static class ParcelableScanResults implements Parcelable {
    444 
    445         public ScanResult mResults[];
    446 
    447         public ParcelableScanResults(ScanResult[] results) {
    448             mResults = results;
    449         }
    450 
    451         public ScanResult[] getResults() {
    452             return mResults;
    453         }
    454 
    455         /** Implement the Parcelable interface {@hide} */
    456         public int describeContents() {
    457             return 0;
    458         }
    459 
    460         /** Implement the Parcelable interface {@hide} */
    461         public void writeToParcel(Parcel dest, int flags) {
    462             if (mResults != null) {
    463                 dest.writeInt(mResults.length);
    464                 for (int i = 0; i < mResults.length; i++) {
    465                     ScanResult result = mResults[i];
    466                     result.writeToParcel(dest, flags);
    467                 }
    468             } else {
    469                 dest.writeInt(0);
    470             }
    471         }
    472 
    473         /** Implement the Parcelable interface {@hide} */
    474         public static final Creator<ParcelableScanResults> CREATOR =
    475                 new Creator<ParcelableScanResults>() {
    476                     public ParcelableScanResults createFromParcel(Parcel in) {
    477                         int n = in.readInt();
    478                         ScanResult results[] = new ScanResult[n];
    479                         for (int i = 0; i < n; i++) {
    480                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    481                         }
    482                         return new ParcelableScanResults(results);
    483                     }
    484 
    485                     public ParcelableScanResults[] newArray(int size) {
    486                         return new ParcelableScanResults[size];
    487                     }
    488                 };
    489     }
    490 
    491     /** {@hide} */
    492     public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
    493     /** {@hide} */
    494     public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
    495     /**
    496      * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
    497      * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
    498      * {@hide}
    499      */
    500     public static class PnoSettings implements Parcelable {
    501         /**
    502          * Pno network to be added to the PNO scan filtering.
    503          * {@hide}
    504          */
    505         public static class PnoNetwork {
    506             /*
    507              * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
    508              */
    509             /** Whether directed scan needs to be performed (for hidden SSIDs) */
    510             public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
    511             /** Whether PNO event shall be triggered if the network is found on A band */
    512             public static final byte FLAG_A_BAND = (1 << 1);
    513             /** Whether PNO event shall be triggered if the network is found on G band */
    514             public static final byte FLAG_G_BAND = (1 << 2);
    515             /**
    516              * Whether strict matching is required
    517              * If required then the firmware must store the network's SSID and not just a hash
    518              */
    519             public static final byte FLAG_STRICT_MATCH = (1 << 3);
    520             /**
    521              * If this SSID should be considered the same network as the currently connected
    522              * one for scoring.
    523              */
    524             public static final byte FLAG_SAME_NETWORK = (1 << 4);
    525 
    526             /*
    527              * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
    528              * {@link #PnoNetwork.authBitField}
    529              */
    530             /** Open Network */
    531             public static final byte AUTH_CODE_OPEN = (1 << 0);
    532             /** WPA_PSK or WPA2PSK */
    533             public static final byte AUTH_CODE_PSK = (1 << 1);
    534             /** any EAPOL */
    535             public static final byte AUTH_CODE_EAPOL = (1 << 2);
    536 
    537             /** SSID of the network */
    538             public String ssid;
    539             /** Network ID in wpa_supplicant */
    540             public int networkId;
    541             /** Assigned priority for the network */
    542             public int priority;
    543             /** Bitmask of the FLAG_XXX */
    544             public byte flags;
    545             /** Bitmask of the ATUH_XXX */
    546             public byte authBitField;
    547 
    548             /**
    549              * default constructor for PnoNetwork
    550              */
    551             public PnoNetwork(String ssid) {
    552                 this.ssid = ssid;
    553                 flags = 0;
    554                 authBitField = 0;
    555             }
    556         }
    557 
    558         /** Connected vs Disconnected PNO flag {@hide} */
    559         public boolean isConnected;
    560         /** Minimum 5GHz RSSI for a BSSID to be considered */
    561         public int min5GHzRssi;
    562         /** Minimum 2.4GHz RSSI for a BSSID to be considered */
    563         public int min24GHzRssi;
    564         /** Maximum score that a network can have before bonuses */
    565         public int initialScoreMax;
    566         /**
    567          *  Only report when there is a network's score this much higher
    568          *  than the current connection.
    569          */
    570         public int currentConnectionBonus;
    571         /** score bonus for all networks with the same network flag */
    572         public int sameNetworkBonus;
    573         /** score bonus for networks that are not open */
    574         public int secureBonus;
    575         /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
    576         public int band5GHzBonus;
    577         /** Pno Network filter list */
    578         public PnoNetwork[] networkList;
    579 
    580         /** Implement the Parcelable interface {@hide} */
    581         public int describeContents() {
    582             return 0;
    583         }
    584 
    585         /** Implement the Parcelable interface {@hide} */
    586         public void writeToParcel(Parcel dest, int flags) {
    587             dest.writeInt(isConnected ? 1 : 0);
    588             dest.writeInt(min5GHzRssi);
    589             dest.writeInt(min24GHzRssi);
    590             dest.writeInt(initialScoreMax);
    591             dest.writeInt(currentConnectionBonus);
    592             dest.writeInt(sameNetworkBonus);
    593             dest.writeInt(secureBonus);
    594             dest.writeInt(band5GHzBonus);
    595             if (networkList != null) {
    596                 dest.writeInt(networkList.length);
    597                 for (int i = 0; i < networkList.length; i++) {
    598                     dest.writeString(networkList[i].ssid);
    599                     dest.writeInt(networkList[i].networkId);
    600                     dest.writeInt(networkList[i].priority);
    601                     dest.writeByte(networkList[i].flags);
    602                     dest.writeByte(networkList[i].authBitField);
    603                 }
    604             } else {
    605                 dest.writeInt(0);
    606             }
    607         }
    608 
    609         /** Implement the Parcelable interface {@hide} */
    610         public static final Creator<PnoSettings> CREATOR =
    611                 new Creator<PnoSettings>() {
    612                     public PnoSettings createFromParcel(Parcel in) {
    613                         PnoSettings settings = new PnoSettings();
    614                         settings.isConnected = in.readInt() == 1;
    615                         settings.min5GHzRssi = in.readInt();
    616                         settings.min24GHzRssi = in.readInt();
    617                         settings.initialScoreMax = in.readInt();
    618                         settings.currentConnectionBonus = in.readInt();
    619                         settings.sameNetworkBonus = in.readInt();
    620                         settings.secureBonus = in.readInt();
    621                         settings.band5GHzBonus = in.readInt();
    622                         int numNetworks = in.readInt();
    623                         settings.networkList = new PnoNetwork[numNetworks];
    624                         for (int i = 0; i < numNetworks; i++) {
    625                             String ssid = in.readString();
    626                             PnoNetwork network = new PnoNetwork(ssid);
    627                             network.networkId = in.readInt();
    628                             network.priority = in.readInt();
    629                             network.flags = in.readByte();
    630                             network.authBitField = in.readByte();
    631                             settings.networkList[i] = network;
    632                         }
    633                         return settings;
    634                     }
    635 
    636                     public PnoSettings[] newArray(int size) {
    637                         return new PnoSettings[size];
    638                     }
    639                 };
    640 
    641     }
    642 
    643     /**
    644      * interface to get scan events on; specify this on {@link #startBackgroundScan} or
    645      * {@link #startScan}
    646      */
    647     public interface ScanListener extends ActionListener {
    648         /**
    649          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
    650          * same period requested. If period of a scan is changed; it is reported by this event.
    651          */
    652         public void onPeriodChanged(int periodInMs);
    653         /**
    654          * reports results retrieved from background scan and single shot scans
    655          */
    656         public void onResults(ScanData[] results);
    657         /**
    658          * reports full scan result for each access point found in scan
    659          */
    660         public void onFullResult(ScanResult fullScanResult);
    661     }
    662 
    663     /**
    664      * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
    665      * {@link #startConnectedPnoScan}.
    666      * {@hide}
    667      */
    668     public interface PnoScanListener extends ScanListener {
    669         /**
    670          * Invoked when one of the PNO networks are found in scan results.
    671          */
    672         void onPnoNetworkFound(ScanResult[] results);
    673     }
    674 
    675     /**
    676      * Register a listener that will receive results from all single scans
    677      * Either the onSuccess/onFailure will be called once when the listener is registered. After
    678      * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
    679      * listener. It is possible that onFullResult will not be called for all results of the first
    680      * scan if the listener was registered during the scan.
    681      *
    682      * @param listener specifies the object to report events to. This object is also treated as a
    683      *                 key for this request, and must also be specified to cancel the request.
    684      *                 Multiple requests should also not share this object.
    685      * {@hide}
    686      */
    687     public void registerScanListener(ScanListener listener) {
    688         Preconditions.checkNotNull(listener, "listener cannot be null");
    689         int key = addListener(listener);
    690         if (key == INVALID_KEY) return;
    691         validateChannel();
    692         mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
    693     }
    694 
    695     /**
    696      * Deregister a listener for ongoing single scans
    697      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    698      *  #registerScanListener}
    699      * {@hide}
    700      */
    701     public void deregisterScanListener(ScanListener listener) {
    702         Preconditions.checkNotNull(listener, "listener cannot be null");
    703         int key = removeListener(listener);
    704         if (key == INVALID_KEY) return;
    705         validateChannel();
    706         mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key);
    707     }
    708 
    709     /** start wifi scan in background
    710      * @param settings specifies various parameters for the scan; for more information look at
    711      * {@link ScanSettings}
    712      * @param listener specifies the object to report events to. This object is also treated as a
    713      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    714      *                 scans should also not share this object.
    715      */
    716     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
    717         startBackgroundScan(settings, listener, null);
    718     }
    719 
    720     /** start wifi scan in background
    721      * @param settings specifies various parameters for the scan; for more information look at
    722      * {@link ScanSettings}
    723      * @param workSource WorkSource to blame for power usage
    724      * @param listener specifies the object to report events to. This object is also treated as a
    725      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    726      *                 scans should also not share this object.
    727      */
    728     public void startBackgroundScan(ScanSettings settings, ScanListener listener,
    729             WorkSource workSource) {
    730         Preconditions.checkNotNull(listener, "listener cannot be null");
    731         int key = addListener(listener);
    732         if (key == INVALID_KEY) return;
    733         validateChannel();
    734         Bundle scanParams = new Bundle();
    735         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
    736         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
    737         mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
    738     }
    739 
    740     /**
    741      * stop an ongoing wifi scan
    742      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    743      *  #startBackgroundScan}
    744      */
    745     public void stopBackgroundScan(ScanListener listener) {
    746         Preconditions.checkNotNull(listener, "listener cannot be null");
    747         int key = removeListener(listener);
    748         if (key == INVALID_KEY) return;
    749         validateChannel();
    750         mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
    751     }
    752     /**
    753      * reports currently available scan results on appropriate listeners
    754      * @return true if all scan results were reported correctly
    755      */
    756     public boolean getScanResults() {
    757         validateChannel();
    758         Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
    759         return reply.what == CMD_OP_SUCCEEDED;
    760     }
    761 
    762     /**
    763      * starts a single scan and reports results asynchronously
    764      * @param settings specifies various parameters for the scan; for more information look at
    765      * {@link ScanSettings}
    766      * @param listener specifies the object to report events to. This object is also treated as a
    767      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    768      *                 scans should also not share this object.
    769      */
    770     public void startScan(ScanSettings settings, ScanListener listener) {
    771         startScan(settings, listener, null);
    772     }
    773 
    774     /**
    775      * starts a single scan and reports results asynchronously
    776      * @param settings specifies various parameters for the scan; for more information look at
    777      * {@link ScanSettings}
    778      * @param workSource WorkSource to blame for power usage
    779      * @param listener specifies the object to report events to. This object is also treated as a
    780      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    781      *                 scans should also not share this object.
    782      */
    783     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
    784         Preconditions.checkNotNull(listener, "listener cannot be null");
    785         int key = addListener(listener);
    786         if (key == INVALID_KEY) return;
    787         validateChannel();
    788         Bundle scanParams = new Bundle();
    789         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
    790         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
    791         mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
    792     }
    793 
    794     /**
    795      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
    796      * hasn't been called on the listener, ignored otherwise
    797      * @param listener
    798      */
    799     public void stopScan(ScanListener listener) {
    800         Preconditions.checkNotNull(listener, "listener cannot be null");
    801         int key = removeListener(listener);
    802         if (key == INVALID_KEY) return;
    803         validateChannel();
    804         mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
    805     }
    806 
    807     private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
    808         // Bundle up both the settings and send it across.
    809         Bundle pnoParams = new Bundle();
    810         // Set the PNO scan flag.
    811         scanSettings.isPnoScan = true;
    812         pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
    813         pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
    814         mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
    815     }
    816     /**
    817      * Start wifi connected PNO scan
    818      * @param scanSettings specifies various parameters for the scan; for more information look at
    819      * {@link ScanSettings}
    820      * @param pnoSettings specifies various parameters for PNO; for more information look at
    821      * {@link PnoSettings}
    822      * @param listener specifies the object to report events to. This object is also treated as a
    823      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    824      *                 scans should also not share this object.
    825      * {@hide}
    826      */
    827     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
    828             PnoScanListener listener) {
    829         Preconditions.checkNotNull(listener, "listener cannot be null");
    830         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
    831         int key = addListener(listener);
    832         if (key == INVALID_KEY) return;
    833         validateChannel();
    834         pnoSettings.isConnected = true;
    835         startPnoScan(scanSettings, pnoSettings, key);
    836     }
    837     /**
    838      * Start wifi disconnected PNO scan
    839      * @param scanSettings specifies various parameters for the scan; for more information look at
    840      * {@link ScanSettings}
    841      * @param pnoSettings specifies various parameters for PNO; for more information look at
    842      * {@link PnoSettings}
    843      * @param listener specifies the object to report events to. This object is also treated as a
    844      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    845      *                 scans should also not share this object.
    846      * {@hide}
    847      */
    848     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
    849             PnoScanListener listener) {
    850         Preconditions.checkNotNull(listener, "listener cannot be null");
    851         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
    852         int key = addListener(listener);
    853         if (key == INVALID_KEY) return;
    854         validateChannel();
    855         pnoSettings.isConnected = false;
    856         startPnoScan(scanSettings, pnoSettings, key);
    857     }
    858     /**
    859      * Stop an ongoing wifi PNO scan
    860      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    861      *  #startPnoScan}
    862      * TODO(rpius): Check if we can remove pnoSettings param in stop.
    863      * {@hide}
    864      */
    865     public void stopPnoScan(ScanListener listener) {
    866         Preconditions.checkNotNull(listener, "listener cannot be null");
    867         int key = removeListener(listener);
    868         if (key == INVALID_KEY) return;
    869         validateChannel();
    870         mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
    871     }
    872 
    873     /** specifies information about an access point of interest */
    874     public static class BssidInfo {
    875         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
    876         public String bssid;
    877         /** low signal strength threshold; more information at {@link ScanResult#level} */
    878         public int low;                                            /* minimum RSSI */
    879         /** high signal threshold; more information at {@link ScanResult#level} */
    880         public int high;                                           /* maximum RSSI */
    881         /** channel frequency (in KHz) where you may find this BSSID */
    882         public int frequencyHint;
    883     }
    884 
    885     /** @hide */
    886     @SystemApi
    887     public static class WifiChangeSettings implements Parcelable {
    888         public int rssiSampleSize;                          /* sample size for RSSI averaging */
    889         public int lostApSampleSize;                        /* samples to confirm AP's loss */
    890         public int unchangedSampleSize;                     /* samples to confirm no change */
    891         public int minApsBreachingThreshold;                /* change threshold to trigger event */
    892         public int periodInMs;                              /* scan period in millisecond */
    893         public BssidInfo[] bssidInfos;
    894 
    895         /** Implement the Parcelable interface {@hide} */
    896         public int describeContents() {
    897             return 0;
    898         }
    899 
    900         /** Implement the Parcelable interface {@hide} */
    901         public void writeToParcel(Parcel dest, int flags) {
    902             dest.writeInt(rssiSampleSize);
    903             dest.writeInt(lostApSampleSize);
    904             dest.writeInt(unchangedSampleSize);
    905             dest.writeInt(minApsBreachingThreshold);
    906             dest.writeInt(periodInMs);
    907             if (bssidInfos != null) {
    908                 dest.writeInt(bssidInfos.length);
    909                 for (int i = 0; i < bssidInfos.length; i++) {
    910                     BssidInfo info = bssidInfos[i];
    911                     dest.writeString(info.bssid);
    912                     dest.writeInt(info.low);
    913                     dest.writeInt(info.high);
    914                     dest.writeInt(info.frequencyHint);
    915                 }
    916             } else {
    917                 dest.writeInt(0);
    918             }
    919         }
    920 
    921         /** Implement the Parcelable interface {@hide} */
    922         public static final Creator<WifiChangeSettings> CREATOR =
    923                 new Creator<WifiChangeSettings>() {
    924                     public WifiChangeSettings createFromParcel(Parcel in) {
    925                         WifiChangeSettings settings = new WifiChangeSettings();
    926                         settings.rssiSampleSize = in.readInt();
    927                         settings.lostApSampleSize = in.readInt();
    928                         settings.unchangedSampleSize = in.readInt();
    929                         settings.minApsBreachingThreshold = in.readInt();
    930                         settings.periodInMs = in.readInt();
    931                         int len = in.readInt();
    932                         settings.bssidInfos = new BssidInfo[len];
    933                         for (int i = 0; i < len; i++) {
    934                             BssidInfo info = new BssidInfo();
    935                             info.bssid = in.readString();
    936                             info.low = in.readInt();
    937                             info.high = in.readInt();
    938                             info.frequencyHint = in.readInt();
    939                             settings.bssidInfos[i] = info;
    940                         }
    941                         return settings;
    942                     }
    943 
    944                     public WifiChangeSettings[] newArray(int size) {
    945                         return new WifiChangeSettings[size];
    946                     }
    947                 };
    948 
    949     }
    950 
    951     /** configure WifiChange detection
    952      * @param rssiSampleSize number of samples used for RSSI averaging
    953      * @param lostApSampleSize number of samples to confirm an access point's loss
    954      * @param unchangedSampleSize number of samples to confirm there are no changes
    955      * @param minApsBreachingThreshold minimum number of access points that need to be
    956      *                                 out of range to detect WifiChange
    957      * @param periodInMs indicates period of scan to find changes
    958      * @param bssidInfos access points to watch
    959      */
    960     public void configureWifiChange(
    961             int rssiSampleSize,                             /* sample size for RSSI averaging */
    962             int lostApSampleSize,                           /* samples to confirm AP's loss */
    963             int unchangedSampleSize,                        /* samples to confirm no change */
    964             int minApsBreachingThreshold,                   /* change threshold to trigger event */
    965             int periodInMs,                                 /* period of scan */
    966             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
    967             )
    968     {
    969         validateChannel();
    970 
    971         WifiChangeSettings settings = new WifiChangeSettings();
    972         settings.rssiSampleSize = rssiSampleSize;
    973         settings.lostApSampleSize = lostApSampleSize;
    974         settings.unchangedSampleSize = unchangedSampleSize;
    975         settings.minApsBreachingThreshold = minApsBreachingThreshold;
    976         settings.periodInMs = periodInMs;
    977         settings.bssidInfos = bssidInfos;
    978 
    979         configureWifiChange(settings);
    980     }
    981 
    982     /**
    983      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
    984      */
    985     public interface WifiChangeListener extends ActionListener {
    986         /** indicates that changes were detected in wifi environment
    987          * @param results indicate the access points that exhibited change
    988          */
    989         public void onChanging(ScanResult[] results);           /* changes are found */
    990         /** indicates that no wifi changes are being detected for a while
    991          * @param results indicate the access points that are bing monitored for change
    992          */
    993         public void onQuiescence(ScanResult[] results);         /* changes settled down */
    994     }
    995 
    996     /**
    997      * track changes in wifi environment
    998      * @param listener object to report events on; this object must be unique and must also be
    999      *                 provided on {@link #stopTrackingWifiChange}
   1000      */
   1001     public void startTrackingWifiChange(WifiChangeListener listener) {
   1002         Preconditions.checkNotNull(listener, "listener cannot be null");
   1003         int key = addListener(listener);
   1004         if (key == INVALID_KEY) return;
   1005         validateChannel();
   1006         mAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
   1007     }
   1008 
   1009     /**
   1010      * stop tracking changes in wifi environment
   1011      * @param listener object that was provided to report events on {@link
   1012      * #stopTrackingWifiChange}
   1013      */
   1014     public void stopTrackingWifiChange(WifiChangeListener listener) {
   1015         int key = removeListener(listener);
   1016         if (key == INVALID_KEY) return;
   1017         validateChannel();
   1018         mAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
   1019     }
   1020 
   1021     /** @hide */
   1022     @SystemApi
   1023     public void configureWifiChange(WifiChangeSettings settings) {
   1024         validateChannel();
   1025         mAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
   1026     }
   1027 
   1028     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
   1029     public static interface BssidListener extends ActionListener {
   1030         /** indicates that access points were found by on going scans
   1031          * @param results list of scan results, one for each access point visible currently
   1032          */
   1033         public void onFound(ScanResult[] results);
   1034         /** indicates that access points were missed by on going scans
   1035          * @param results list of scan results, for each access point that is not visible anymore
   1036          */
   1037         public void onLost(ScanResult[] results);
   1038     }
   1039 
   1040     /** @hide */
   1041     @SystemApi
   1042     public static class HotlistSettings implements Parcelable {
   1043         public BssidInfo[] bssidInfos;
   1044         public int apLostThreshold;
   1045 
   1046         /** Implement the Parcelable interface {@hide} */
   1047         public int describeContents() {
   1048             return 0;
   1049         }
   1050 
   1051         /** Implement the Parcelable interface {@hide} */
   1052         public void writeToParcel(Parcel dest, int flags) {
   1053             dest.writeInt(apLostThreshold);
   1054 
   1055             if (bssidInfos != null) {
   1056                 dest.writeInt(bssidInfos.length);
   1057                 for (int i = 0; i < bssidInfos.length; i++) {
   1058                     BssidInfo info = bssidInfos[i];
   1059                     dest.writeString(info.bssid);
   1060                     dest.writeInt(info.low);
   1061                     dest.writeInt(info.high);
   1062                     dest.writeInt(info.frequencyHint);
   1063                 }
   1064             } else {
   1065                 dest.writeInt(0);
   1066             }
   1067         }
   1068 
   1069         /** Implement the Parcelable interface {@hide} */
   1070         public static final Creator<HotlistSettings> CREATOR =
   1071                 new Creator<HotlistSettings>() {
   1072                     public HotlistSettings createFromParcel(Parcel in) {
   1073                         HotlistSettings settings = new HotlistSettings();
   1074                         settings.apLostThreshold = in.readInt();
   1075                         int n = in.readInt();
   1076                         settings.bssidInfos = new BssidInfo[n];
   1077                         for (int i = 0; i < n; i++) {
   1078                             BssidInfo info = new BssidInfo();
   1079                             info.bssid = in.readString();
   1080                             info.low = in.readInt();
   1081                             info.high = in.readInt();
   1082                             info.frequencyHint = in.readInt();
   1083                             settings.bssidInfos[i] = info;
   1084                         }
   1085                         return settings;
   1086                     }
   1087 
   1088                     public HotlistSettings[] newArray(int size) {
   1089                         return new HotlistSettings[size];
   1090                     }
   1091                 };
   1092     }
   1093 
   1094     /**
   1095      * set interesting access points to find
   1096      * @param bssidInfos access points of interest
   1097      * @param apLostThreshold number of scans needed to indicate that AP is lost
   1098      * @param listener object provided to report events on; this object must be unique and must
   1099      *                 also be provided on {@link #stopTrackingBssids}
   1100      */
   1101     public void startTrackingBssids(BssidInfo[] bssidInfos,
   1102                                     int apLostThreshold, BssidListener listener) {
   1103         Preconditions.checkNotNull(listener, "listener cannot be null");
   1104         int key = addListener(listener);
   1105         if (key == INVALID_KEY) return;
   1106         validateChannel();
   1107         HotlistSettings settings = new HotlistSettings();
   1108         settings.bssidInfos = bssidInfos;
   1109         settings.apLostThreshold = apLostThreshold;
   1110         mAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
   1111     }
   1112 
   1113     /**
   1114      * remove tracking of interesting access points
   1115      * @param listener same object provided in {@link #startTrackingBssids}
   1116      */
   1117     public void stopTrackingBssids(BssidListener listener) {
   1118         Preconditions.checkNotNull(listener, "listener cannot be null");
   1119         int key = removeListener(listener);
   1120         if (key == INVALID_KEY) return;
   1121         validateChannel();
   1122         mAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
   1123     }
   1124 
   1125 
   1126     /* private members and methods */
   1127 
   1128     private static final String TAG = "WifiScanner";
   1129     private static final boolean DBG = false;
   1130 
   1131     /* commands for Wifi Service */
   1132     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
   1133 
   1134     /** @hide */
   1135     public static final int CMD_SCAN                        = BASE + 0;
   1136     /** @hide */
   1137     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
   1138     /** @hide */
   1139     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
   1140     /** @hide */
   1141     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
   1142     /** @hide */
   1143     public static final int CMD_SCAN_RESULT                 = BASE + 5;
   1144     /** @hide */
   1145     public static final int CMD_SET_HOTLIST                 = BASE + 6;
   1146     /** @hide */
   1147     public static final int CMD_RESET_HOTLIST               = BASE + 7;
   1148     /** @hide */
   1149     public static final int CMD_AP_FOUND                    = BASE + 9;
   1150     /** @hide */
   1151     public static final int CMD_AP_LOST                     = BASE + 10;
   1152     /** @hide */
   1153     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
   1154     /** @hide */
   1155     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
   1156     /** @hide */
   1157     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
   1158     /** @hide */
   1159     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
   1160     /** @hide */
   1161     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
   1162     /** @hide */
   1163     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
   1164     /** @hide */
   1165     public static final int CMD_OP_FAILED                   = BASE + 18;
   1166     /** @hide */
   1167     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
   1168     /** @hide */
   1169     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
   1170     /** @hide */
   1171     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
   1172     /** @hide */
   1173     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
   1174     /** @hide */
   1175     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
   1176     /** @hide */
   1177     public static final int CMD_START_PNO_SCAN              = BASE + 24;
   1178     /** @hide */
   1179     public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
   1180     /** @hide */
   1181     public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
   1182     /** @hide */
   1183     public static final int CMD_REGISTER_SCAN_LISTENER      = BASE + 27;
   1184     /** @hide */
   1185     public static final int CMD_DEREGISTER_SCAN_LISTENER    = BASE + 28;
   1186 
   1187     private Context mContext;
   1188     private IWifiScanner mService;
   1189 
   1190     private static final int INVALID_KEY = 0;
   1191     private int mListenerKey = 1;
   1192 
   1193     private final SparseArray mListenerMap = new SparseArray();
   1194     private final Object mListenerMapLock = new Object();
   1195 
   1196     private AsyncChannel mAsyncChannel;
   1197     private final Handler mInternalHandler;
   1198 
   1199     /**
   1200      * Create a new WifiScanner instance.
   1201      * Applications will almost always want to use
   1202      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
   1203      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
   1204      * @param context the application context
   1205      * @param service the Binder interface
   1206      * @param looper the Looper used to deliver callbacks
   1207      * @hide
   1208      */
   1209     public WifiScanner(Context context, IWifiScanner service, Looper looper) {
   1210         mContext = context;
   1211         mService = service;
   1212 
   1213         Messenger messenger = null;
   1214         try {
   1215             messenger = mService.getMessenger();
   1216         } catch (RemoteException e) {
   1217             throw e.rethrowFromSystemServer();
   1218         }
   1219 
   1220         if (messenger == null) {
   1221             throw new IllegalStateException("getMessenger() returned null!  This is invalid.");
   1222         }
   1223 
   1224         mAsyncChannel = new AsyncChannel();
   1225 
   1226         mInternalHandler = new ServiceHandler(looper);
   1227         mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
   1228         // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
   1229         // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
   1230         mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
   1231     }
   1232 
   1233     private void validateChannel() {
   1234         if (mAsyncChannel == null) throw new IllegalStateException(
   1235                 "No permission to access and change wifi or a bad initialization");
   1236     }
   1237 
   1238     // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
   1239     // send an error message to internal handler; Otherwise add the listener to the listener map and
   1240     // return the key of the listener.
   1241     private int addListener(ActionListener listener) {
   1242         synchronized (mListenerMapLock) {
   1243             boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
   1244             // Note we need to put the listener into listener map even if it's a duplicate as the
   1245             // internal handler will need the key to find the listener. In case of duplicates,
   1246             // removing duplicate key logic will be handled in internal handler.
   1247             int key = putListener(listener);
   1248             if (keyExists) {
   1249                 if (DBG) Log.d(TAG, "listener key already exists");
   1250                 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
   1251                         "Outstanding request with same key not stopped yet");
   1252                 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
   1253                         operationResult);
   1254                 message.sendToTarget();
   1255                 return INVALID_KEY;
   1256             } else {
   1257                 return key;
   1258             }
   1259         }
   1260     }
   1261 
   1262     private int putListener(Object listener) {
   1263         if (listener == null) return INVALID_KEY;
   1264         int key;
   1265         synchronized (mListenerMapLock) {
   1266             do {
   1267                 key = mListenerKey++;
   1268             } while (key == INVALID_KEY);
   1269             mListenerMap.put(key, listener);
   1270         }
   1271         return key;
   1272     }
   1273 
   1274     private Object getListener(int key) {
   1275         if (key == INVALID_KEY) return null;
   1276         synchronized (mListenerMapLock) {
   1277             Object listener = mListenerMap.get(key);
   1278             return listener;
   1279         }
   1280     }
   1281 
   1282     private int getListenerKey(Object listener) {
   1283         if (listener == null) return INVALID_KEY;
   1284         synchronized (mListenerMapLock) {
   1285             int index = mListenerMap.indexOfValue(listener);
   1286             if (index == -1) {
   1287                 return INVALID_KEY;
   1288             } else {
   1289                 return mListenerMap.keyAt(index);
   1290             }
   1291         }
   1292     }
   1293 
   1294     private Object removeListener(int key) {
   1295         if (key == INVALID_KEY) return null;
   1296         synchronized (mListenerMapLock) {
   1297             Object listener = mListenerMap.get(key);
   1298             mListenerMap.remove(key);
   1299             return listener;
   1300         }
   1301     }
   1302 
   1303     private int removeListener(Object listener) {
   1304         int key = getListenerKey(listener);
   1305         if (key == INVALID_KEY) {
   1306             Log.e(TAG, "listener cannot be found");
   1307             return key;
   1308         }
   1309         synchronized (mListenerMapLock) {
   1310             mListenerMap.remove(key);
   1311             return key;
   1312         }
   1313     }
   1314 
   1315     /** @hide */
   1316     public static class OperationResult implements Parcelable {
   1317         public int reason;
   1318         public String description;
   1319 
   1320         public OperationResult(int reason, String description) {
   1321             this.reason = reason;
   1322             this.description = description;
   1323         }
   1324 
   1325         /** Implement the Parcelable interface {@hide} */
   1326         public int describeContents() {
   1327             return 0;
   1328         }
   1329 
   1330         /** Implement the Parcelable interface {@hide} */
   1331         public void writeToParcel(Parcel dest, int flags) {
   1332             dest.writeInt(reason);
   1333             dest.writeString(description);
   1334         }
   1335 
   1336         /** Implement the Parcelable interface {@hide} */
   1337         public static final Creator<OperationResult> CREATOR =
   1338                 new Creator<OperationResult>() {
   1339                     public OperationResult createFromParcel(Parcel in) {
   1340                         int reason = in.readInt();
   1341                         String description = in.readString();
   1342                         return new OperationResult(reason, description);
   1343                     }
   1344 
   1345                     public OperationResult[] newArray(int size) {
   1346                         return new OperationResult[size];
   1347                     }
   1348                 };
   1349     }
   1350 
   1351     private class ServiceHandler extends Handler {
   1352         ServiceHandler(Looper looper) {
   1353             super(looper);
   1354         }
   1355         @Override
   1356         public void handleMessage(Message msg) {
   1357             switch (msg.what) {
   1358                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
   1359                     return;
   1360                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
   1361                     Log.e(TAG, "Channel connection lost");
   1362                     // This will cause all further async API calls on the WifiManager
   1363                     // to fail and throw an exception
   1364                     mAsyncChannel = null;
   1365                     getLooper().quit();
   1366                     return;
   1367             }
   1368 
   1369             Object listener = getListener(msg.arg2);
   1370 
   1371             if (listener == null) {
   1372                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
   1373                 return;
   1374             } else {
   1375                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
   1376             }
   1377 
   1378             switch (msg.what) {
   1379                     /* ActionListeners grouped together */
   1380                 case CMD_OP_SUCCEEDED :
   1381                     ((ActionListener) listener).onSuccess();
   1382                     break;
   1383                 case CMD_OP_FAILED : {
   1384                         OperationResult result = (OperationResult)msg.obj;
   1385                         ((ActionListener) listener).onFailure(result.reason, result.description);
   1386                         removeListener(msg.arg2);
   1387                     }
   1388                     break;
   1389                 case CMD_SCAN_RESULT :
   1390                     ((ScanListener) listener).onResults(
   1391                             ((ParcelableScanData) msg.obj).getResults());
   1392                     return;
   1393                 case CMD_FULL_SCAN_RESULT :
   1394                     ScanResult result = (ScanResult) msg.obj;
   1395                     ((ScanListener) listener).onFullResult(result);
   1396                     return;
   1397                 case CMD_PERIOD_CHANGED:
   1398                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
   1399                     return;
   1400                 case CMD_AP_FOUND:
   1401                     ((BssidListener) listener).onFound(
   1402                             ((ParcelableScanResults) msg.obj).getResults());
   1403                     return;
   1404                 case CMD_AP_LOST:
   1405                     ((BssidListener) listener).onLost(
   1406                             ((ParcelableScanResults) msg.obj).getResults());
   1407                     return;
   1408                 case CMD_WIFI_CHANGE_DETECTED:
   1409                     ((WifiChangeListener) listener).onChanging(
   1410                             ((ParcelableScanResults) msg.obj).getResults());
   1411                    return;
   1412                 case CMD_WIFI_CHANGES_STABILIZED:
   1413                     ((WifiChangeListener) listener).onQuiescence(
   1414                             ((ParcelableScanResults) msg.obj).getResults());
   1415                     return;
   1416                 case CMD_SINGLE_SCAN_COMPLETED:
   1417                     if (DBG) Log.d(TAG, "removing listener for single scan");
   1418                     removeListener(msg.arg2);
   1419                     break;
   1420                 case CMD_PNO_NETWORK_FOUND:
   1421                     ((PnoScanListener) listener).onPnoNetworkFound(
   1422                             ((ParcelableScanResults) msg.obj).getResults());
   1423                     return;
   1424                 default:
   1425                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
   1426                     return;
   1427             }
   1428         }
   1429     }
   1430 }
   1431