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