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         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
    290         private ScanResult mResults[];
    291 
    292         ScanData() {}
    293 
    294         public ScanData(int id, int flags, ScanResult[] results) {
    295             mId = id;
    296             mFlags = flags;
    297             mResults = results;
    298         }
    299 
    300         /** {@hide} */
    301         public ScanData(int id, int flags, int bucketsScanned, ScanResult[] results) {
    302             mId = id;
    303             mFlags = flags;
    304             mBucketsScanned = bucketsScanned;
    305             mResults = results;
    306         }
    307 
    308         public ScanData(ScanData s) {
    309             mId = s.mId;
    310             mFlags = s.mFlags;
    311             mBucketsScanned = s.mBucketsScanned;
    312             mResults = new ScanResult[s.mResults.length];
    313             for (int i = 0; i < s.mResults.length; i++) {
    314                 ScanResult result = s.mResults[i];
    315                 ScanResult newResult = new ScanResult(result);
    316                 mResults[i] = newResult;
    317             }
    318         }
    319 
    320         public int getId() {
    321             return mId;
    322         }
    323 
    324         public int getFlags() {
    325             return mFlags;
    326         }
    327 
    328         /** {@hide} */
    329         public int getBucketsScanned() {
    330             return mBucketsScanned;
    331         }
    332 
    333         public ScanResult[] getResults() {
    334             return mResults;
    335         }
    336 
    337         /** Implement the Parcelable interface {@hide} */
    338         public int describeContents() {
    339             return 0;
    340         }
    341 
    342         /** Implement the Parcelable interface {@hide} */
    343         public void writeToParcel(Parcel dest, int flags) {
    344             if (mResults != null) {
    345                 dest.writeInt(mId);
    346                 dest.writeInt(mFlags);
    347                 dest.writeInt(mBucketsScanned);
    348                 dest.writeInt(mResults.length);
    349                 for (int i = 0; i < mResults.length; i++) {
    350                     ScanResult result = mResults[i];
    351                     result.writeToParcel(dest, flags);
    352                 }
    353             } else {
    354                 dest.writeInt(0);
    355             }
    356         }
    357 
    358         /** Implement the Parcelable interface {@hide} */
    359         public static final Creator<ScanData> CREATOR =
    360                 new Creator<ScanData>() {
    361                     public ScanData createFromParcel(Parcel in) {
    362                         int id = in.readInt();
    363                         int flags = in.readInt();
    364                         int bucketsScanned = in.readInt();
    365                         int n = in.readInt();
    366                         ScanResult results[] = new ScanResult[n];
    367                         for (int i = 0; i < n; i++) {
    368                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    369                         }
    370                         return new ScanData(id, flags, bucketsScanned, results);
    371                     }
    372 
    373                     public ScanData[] newArray(int size) {
    374                         return new ScanData[size];
    375                     }
    376                 };
    377     }
    378 
    379     public static class ParcelableScanData implements Parcelable {
    380 
    381         public ScanData mResults[];
    382 
    383         public ParcelableScanData(ScanData[] results) {
    384             mResults = results;
    385         }
    386 
    387         public ScanData[] getResults() {
    388             return mResults;
    389         }
    390 
    391         /** Implement the Parcelable interface {@hide} */
    392         public int describeContents() {
    393             return 0;
    394         }
    395 
    396         /** Implement the Parcelable interface {@hide} */
    397         public void writeToParcel(Parcel dest, int flags) {
    398             if (mResults != null) {
    399                 dest.writeInt(mResults.length);
    400                 for (int i = 0; i < mResults.length; i++) {
    401                     ScanData result = mResults[i];
    402                     result.writeToParcel(dest, flags);
    403                 }
    404             } else {
    405                 dest.writeInt(0);
    406             }
    407         }
    408 
    409         /** Implement the Parcelable interface {@hide} */
    410         public static final Creator<ParcelableScanData> CREATOR =
    411                 new Creator<ParcelableScanData>() {
    412                     public ParcelableScanData createFromParcel(Parcel in) {
    413                         int n = in.readInt();
    414                         ScanData results[] = new ScanData[n];
    415                         for (int i = 0; i < n; i++) {
    416                             results[i] = ScanData.CREATOR.createFromParcel(in);
    417                         }
    418                         return new ParcelableScanData(results);
    419                     }
    420 
    421                     public ParcelableScanData[] newArray(int size) {
    422                         return new ParcelableScanData[size];
    423                     }
    424                 };
    425     }
    426 
    427     public static class ParcelableScanResults implements Parcelable {
    428 
    429         public ScanResult mResults[];
    430 
    431         public ParcelableScanResults(ScanResult[] results) {
    432             mResults = results;
    433         }
    434 
    435         public ScanResult[] getResults() {
    436             return mResults;
    437         }
    438 
    439         /** Implement the Parcelable interface {@hide} */
    440         public int describeContents() {
    441             return 0;
    442         }
    443 
    444         /** Implement the Parcelable interface {@hide} */
    445         public void writeToParcel(Parcel dest, int flags) {
    446             if (mResults != null) {
    447                 dest.writeInt(mResults.length);
    448                 for (int i = 0; i < mResults.length; i++) {
    449                     ScanResult result = mResults[i];
    450                     result.writeToParcel(dest, flags);
    451                 }
    452             } else {
    453                 dest.writeInt(0);
    454             }
    455         }
    456 
    457         /** Implement the Parcelable interface {@hide} */
    458         public static final Creator<ParcelableScanResults> CREATOR =
    459                 new Creator<ParcelableScanResults>() {
    460                     public ParcelableScanResults createFromParcel(Parcel in) {
    461                         int n = in.readInt();
    462                         ScanResult results[] = new ScanResult[n];
    463                         for (int i = 0; i < n; i++) {
    464                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    465                         }
    466                         return new ParcelableScanResults(results);
    467                     }
    468 
    469                     public ParcelableScanResults[] newArray(int size) {
    470                         return new ParcelableScanResults[size];
    471                     }
    472                 };
    473     }
    474 
    475     /** {@hide} */
    476     public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
    477     /** {@hide} */
    478     public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
    479     /**
    480      * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
    481      * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
    482      * {@hide}
    483      */
    484     public static class PnoSettings implements Parcelable {
    485         /**
    486          * Pno network to be added to the PNO scan filtering.
    487          * {@hide}
    488          */
    489         public static class PnoNetwork {
    490             /*
    491              * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
    492              */
    493             /** Whether directed scan needs to be performed (for hidden SSIDs) */
    494             public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
    495             /** Whether PNO event shall be triggered if the network is found on A band */
    496             public static final byte FLAG_A_BAND = (1 << 1);
    497             /** Whether PNO event shall be triggered if the network is found on G band */
    498             public static final byte FLAG_G_BAND = (1 << 2);
    499             /**
    500              * Whether strict matching is required
    501              * If required then the firmware must store the network's SSID and not just a hash
    502              */
    503             public static final byte FLAG_STRICT_MATCH = (1 << 3);
    504             /**
    505              * If this SSID should be considered the same network as the currently connected
    506              * one for scoring.
    507              */
    508             public static final byte FLAG_SAME_NETWORK = (1 << 4);
    509 
    510             /*
    511              * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
    512              * {@link #PnoNetwork.authBitField}
    513              */
    514             /** Open Network */
    515             public static final byte AUTH_CODE_OPEN = (1 << 0);
    516             /** WPA_PSK or WPA2PSK */
    517             public static final byte AUTH_CODE_PSK = (1 << 1);
    518             /** any EAPOL */
    519             public static final byte AUTH_CODE_EAPOL = (1 << 2);
    520 
    521             /** SSID of the network */
    522             public String ssid;
    523             /** Network ID in wpa_supplicant */
    524             public int networkId;
    525             /** Assigned priority for the network */
    526             public int priority;
    527             /** Bitmask of the FLAG_XXX */
    528             public byte flags;
    529             /** Bitmask of the ATUH_XXX */
    530             public byte authBitField;
    531 
    532             /**
    533              * default constructor for PnoNetwork
    534              */
    535             public PnoNetwork(String ssid) {
    536                 this.ssid = ssid;
    537                 flags = 0;
    538                 authBitField = 0;
    539             }
    540         }
    541 
    542         /** Connected vs Disconnected PNO flag {@hide} */
    543         public boolean isConnected;
    544         /** Minimum 5GHz RSSI for a BSSID to be considered */
    545         public int min5GHzRssi;
    546         /** Minimum 2.4GHz RSSI for a BSSID to be considered */
    547         public int min24GHzRssi;
    548         /** Maximum score that a network can have before bonuses */
    549         public int initialScoreMax;
    550         /**
    551          *  Only report when there is a network's score this much higher
    552          *  than the current connection.
    553          */
    554         public int currentConnectionBonus;
    555         /** score bonus for all networks with the same network flag */
    556         public int sameNetworkBonus;
    557         /** score bonus for networks that are not open */
    558         public int secureBonus;
    559         /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
    560         public int band5GHzBonus;
    561         /** Pno Network filter list */
    562         public PnoNetwork[] networkList;
    563 
    564         /** Implement the Parcelable interface {@hide} */
    565         public int describeContents() {
    566             return 0;
    567         }
    568 
    569         /** Implement the Parcelable interface {@hide} */
    570         public void writeToParcel(Parcel dest, int flags) {
    571             dest.writeInt(isConnected ? 1 : 0);
    572             dest.writeInt(min5GHzRssi);
    573             dest.writeInt(min24GHzRssi);
    574             dest.writeInt(initialScoreMax);
    575             dest.writeInt(currentConnectionBonus);
    576             dest.writeInt(sameNetworkBonus);
    577             dest.writeInt(secureBonus);
    578             dest.writeInt(band5GHzBonus);
    579             if (networkList != null) {
    580                 dest.writeInt(networkList.length);
    581                 for (int i = 0; i < networkList.length; i++) {
    582                     dest.writeString(networkList[i].ssid);
    583                     dest.writeInt(networkList[i].networkId);
    584                     dest.writeInt(networkList[i].priority);
    585                     dest.writeByte(networkList[i].flags);
    586                     dest.writeByte(networkList[i].authBitField);
    587                 }
    588             } else {
    589                 dest.writeInt(0);
    590             }
    591         }
    592 
    593         /** Implement the Parcelable interface {@hide} */
    594         public static final Creator<PnoSettings> CREATOR =
    595                 new Creator<PnoSettings>() {
    596                     public PnoSettings createFromParcel(Parcel in) {
    597                         PnoSettings settings = new PnoSettings();
    598                         settings.isConnected = in.readInt() == 1;
    599                         settings.min5GHzRssi = in.readInt();
    600                         settings.min24GHzRssi = in.readInt();
    601                         settings.initialScoreMax = in.readInt();
    602                         settings.currentConnectionBonus = in.readInt();
    603                         settings.sameNetworkBonus = in.readInt();
    604                         settings.secureBonus = in.readInt();
    605                         settings.band5GHzBonus = in.readInt();
    606                         int numNetworks = in.readInt();
    607                         settings.networkList = new PnoNetwork[numNetworks];
    608                         for (int i = 0; i < numNetworks; i++) {
    609                             String ssid = in.readString();
    610                             PnoNetwork network = new PnoNetwork(ssid);
    611                             network.networkId = in.readInt();
    612                             network.priority = in.readInt();
    613                             network.flags = in.readByte();
    614                             network.authBitField = in.readByte();
    615                             settings.networkList[i] = network;
    616                         }
    617                         return settings;
    618                     }
    619 
    620                     public PnoSettings[] newArray(int size) {
    621                         return new PnoSettings[size];
    622                     }
    623                 };
    624 
    625     }
    626 
    627     /**
    628      * interface to get scan events on; specify this on {@link #startBackgroundScan} or
    629      * {@link #startScan}
    630      */
    631     public interface ScanListener extends ActionListener {
    632         /**
    633          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
    634          * same period requested. If period of a scan is changed; it is reported by this event.
    635          */
    636         public void onPeriodChanged(int periodInMs);
    637         /**
    638          * reports results retrieved from background scan and single shot scans
    639          */
    640         public void onResults(ScanData[] results);
    641         /**
    642          * reports full scan result for each access point found in scan
    643          */
    644         public void onFullResult(ScanResult fullScanResult);
    645     }
    646 
    647     /**
    648      * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
    649      * {@link #startConnectedPnoScan}.
    650      * {@hide}
    651      */
    652     public interface PnoScanListener extends ScanListener {
    653         /**
    654          * Invoked when one of the PNO networks are found in scan results.
    655          */
    656         void onPnoNetworkFound(ScanResult[] results);
    657     }
    658 
    659     /** start wifi scan in background
    660      * @param settings specifies various parameters for the scan; for more information look at
    661      * {@link ScanSettings}
    662      * @param listener specifies the object to report events to. This object is also treated as a
    663      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    664      *                 scans should also not share this object.
    665      */
    666     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
    667         startBackgroundScan(settings, listener, null);
    668     }
    669 
    670     /** start wifi scan in background
    671      * @param settings specifies various parameters for the scan; for more information look at
    672      * {@link ScanSettings}
    673      * @param workSource WorkSource to blame for power usage
    674      * @param listener specifies the object to report events to. This object is also treated as a
    675      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    676      *                 scans should also not share this object.
    677      */
    678     public void startBackgroundScan(ScanSettings settings, ScanListener listener,
    679             WorkSource workSource) {
    680         Preconditions.checkNotNull(listener, "listener cannot be null");
    681         int key = addListener(listener);
    682         if (key == INVALID_KEY) return;
    683         validateChannel();
    684         Bundle scanParams = new Bundle();
    685         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
    686         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
    687         mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
    688     }
    689 
    690     /**
    691      * stop an ongoing wifi scan
    692      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    693      *  #startBackgroundScan}
    694      */
    695     public void stopBackgroundScan(ScanListener listener) {
    696         Preconditions.checkNotNull(listener, "listener cannot be null");
    697         int key = removeListener(listener);
    698         if (key == INVALID_KEY) return;
    699         validateChannel();
    700         mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
    701     }
    702     /**
    703      * reports currently available scan results on appropriate listeners
    704      * @return true if all scan results were reported correctly
    705      */
    706     public boolean getScanResults() {
    707         validateChannel();
    708         Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
    709         return reply.what == CMD_OP_SUCCEEDED;
    710     }
    711 
    712     /**
    713      * starts a single scan and reports results asynchronously
    714      * @param settings specifies various parameters for the scan; for more information look at
    715      * {@link ScanSettings}
    716      * @param listener specifies the object to report events to. This object is also treated as a
    717      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    718      *                 scans should also not share this object.
    719      */
    720     public void startScan(ScanSettings settings, ScanListener listener) {
    721         startScan(settings, listener, null);
    722     }
    723 
    724     /**
    725      * starts a single scan and reports results asynchronously
    726      * @param settings specifies various parameters for the scan; for more information look at
    727      * {@link ScanSettings}
    728      * @param workSource WorkSource to blame for power usage
    729      * @param listener specifies the object to report events to. This object is also treated as a
    730      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    731      *                 scans should also not share this object.
    732      */
    733     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
    734         Preconditions.checkNotNull(listener, "listener cannot be null");
    735         int key = addListener(listener);
    736         if (key == INVALID_KEY) return;
    737         validateChannel();
    738         Bundle scanParams = new Bundle();
    739         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
    740         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
    741         mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
    742     }
    743 
    744     /**
    745      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
    746      * hasn't been called on the listener, ignored otherwise
    747      * @param listener
    748      */
    749     public void stopScan(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_STOP_SINGLE_SCAN, 0, key);
    755     }
    756 
    757     private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
    758         // Bundle up both the settings and send it across.
    759         Bundle pnoParams = new Bundle();
    760         // Set the PNO scan flag.
    761         scanSettings.isPnoScan = true;
    762         pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
    763         pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
    764         mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
    765     }
    766     /**
    767      * Start wifi connected PNO scan
    768      * @param scanSettings specifies various parameters for the scan; for more information look at
    769      * {@link ScanSettings}
    770      * @param pnoSettings specifies various parameters for PNO; for more information look at
    771      * {@link PnoSettings}
    772      * @param listener specifies the object to report events to. This object is also treated as a
    773      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    774      *                 scans should also not share this object.
    775      * {@hide}
    776      */
    777     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
    778             PnoScanListener listener) {
    779         Preconditions.checkNotNull(listener, "listener cannot be null");
    780         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
    781         int key = addListener(listener);
    782         if (key == INVALID_KEY) return;
    783         validateChannel();
    784         pnoSettings.isConnected = true;
    785         startPnoScan(scanSettings, pnoSettings, key);
    786     }
    787     /**
    788      * Start wifi disconnected PNO scan
    789      * @param scanSettings specifies various parameters for the scan; for more information look at
    790      * {@link ScanSettings}
    791      * @param pnoSettings specifies various parameters for PNO; for more information look at
    792      * {@link PnoSettings}
    793      * @param listener specifies the object to report events to. This object is also treated as a
    794      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    795      *                 scans should also not share this object.
    796      * {@hide}
    797      */
    798     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
    799             PnoScanListener listener) {
    800         Preconditions.checkNotNull(listener, "listener cannot be null");
    801         Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
    802         int key = addListener(listener);
    803         if (key == INVALID_KEY) return;
    804         validateChannel();
    805         pnoSettings.isConnected = false;
    806         startPnoScan(scanSettings, pnoSettings, key);
    807     }
    808     /**
    809      * Stop an ongoing wifi PNO scan
    810      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    811      *  #startPnoScan}
    812      * TODO(rpius): Check if we can remove pnoSettings param in stop.
    813      * {@hide}
    814      */
    815     public void stopPnoScan(ScanListener listener) {
    816         Preconditions.checkNotNull(listener, "listener cannot be null");
    817         int key = removeListener(listener);
    818         if (key == INVALID_KEY) return;
    819         validateChannel();
    820         mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
    821     }
    822 
    823     /** specifies information about an access point of interest */
    824     public static class BssidInfo {
    825         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
    826         public String bssid;
    827         /** low signal strength threshold; more information at {@link ScanResult#level} */
    828         public int low;                                            /* minimum RSSI */
    829         /** high signal threshold; more information at {@link ScanResult#level} */
    830         public int high;                                           /* maximum RSSI */
    831         /** channel frequency (in KHz) where you may find this BSSID */
    832         public int frequencyHint;
    833     }
    834 
    835     /** @hide */
    836     @SystemApi
    837     public static class WifiChangeSettings implements Parcelable {
    838         public int rssiSampleSize;                          /* sample size for RSSI averaging */
    839         public int lostApSampleSize;                        /* samples to confirm AP's loss */
    840         public int unchangedSampleSize;                     /* samples to confirm no change */
    841         public int minApsBreachingThreshold;                /* change threshold to trigger event */
    842         public int periodInMs;                              /* scan period in millisecond */
    843         public BssidInfo[] bssidInfos;
    844 
    845         /** Implement the Parcelable interface {@hide} */
    846         public int describeContents() {
    847             return 0;
    848         }
    849 
    850         /** Implement the Parcelable interface {@hide} */
    851         public void writeToParcel(Parcel dest, int flags) {
    852             dest.writeInt(rssiSampleSize);
    853             dest.writeInt(lostApSampleSize);
    854             dest.writeInt(unchangedSampleSize);
    855             dest.writeInt(minApsBreachingThreshold);
    856             dest.writeInt(periodInMs);
    857             if (bssidInfos != null) {
    858                 dest.writeInt(bssidInfos.length);
    859                 for (int i = 0; i < bssidInfos.length; i++) {
    860                     BssidInfo info = bssidInfos[i];
    861                     dest.writeString(info.bssid);
    862                     dest.writeInt(info.low);
    863                     dest.writeInt(info.high);
    864                     dest.writeInt(info.frequencyHint);
    865                 }
    866             } else {
    867                 dest.writeInt(0);
    868             }
    869         }
    870 
    871         /** Implement the Parcelable interface {@hide} */
    872         public static final Creator<WifiChangeSettings> CREATOR =
    873                 new Creator<WifiChangeSettings>() {
    874                     public WifiChangeSettings createFromParcel(Parcel in) {
    875                         WifiChangeSettings settings = new WifiChangeSettings();
    876                         settings.rssiSampleSize = in.readInt();
    877                         settings.lostApSampleSize = in.readInt();
    878                         settings.unchangedSampleSize = in.readInt();
    879                         settings.minApsBreachingThreshold = in.readInt();
    880                         settings.periodInMs = in.readInt();
    881                         int len = in.readInt();
    882                         settings.bssidInfos = new BssidInfo[len];
    883                         for (int i = 0; i < len; i++) {
    884                             BssidInfo info = new BssidInfo();
    885                             info.bssid = in.readString();
    886                             info.low = in.readInt();
    887                             info.high = in.readInt();
    888                             info.frequencyHint = in.readInt();
    889                             settings.bssidInfos[i] = info;
    890                         }
    891                         return settings;
    892                     }
    893 
    894                     public WifiChangeSettings[] newArray(int size) {
    895                         return new WifiChangeSettings[size];
    896                     }
    897                 };
    898 
    899     }
    900 
    901     /** configure WifiChange detection
    902      * @param rssiSampleSize number of samples used for RSSI averaging
    903      * @param lostApSampleSize number of samples to confirm an access point's loss
    904      * @param unchangedSampleSize number of samples to confirm there are no changes
    905      * @param minApsBreachingThreshold minimum number of access points that need to be
    906      *                                 out of range to detect WifiChange
    907      * @param periodInMs indicates period of scan to find changes
    908      * @param bssidInfos access points to watch
    909      */
    910     public void configureWifiChange(
    911             int rssiSampleSize,                             /* sample size for RSSI averaging */
    912             int lostApSampleSize,                           /* samples to confirm AP's loss */
    913             int unchangedSampleSize,                        /* samples to confirm no change */
    914             int minApsBreachingThreshold,                   /* change threshold to trigger event */
    915             int periodInMs,                                 /* period of scan */
    916             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
    917             )
    918     {
    919         validateChannel();
    920 
    921         WifiChangeSettings settings = new WifiChangeSettings();
    922         settings.rssiSampleSize = rssiSampleSize;
    923         settings.lostApSampleSize = lostApSampleSize;
    924         settings.unchangedSampleSize = unchangedSampleSize;
    925         settings.minApsBreachingThreshold = minApsBreachingThreshold;
    926         settings.periodInMs = periodInMs;
    927         settings.bssidInfos = bssidInfos;
    928 
    929         configureWifiChange(settings);
    930     }
    931 
    932     /**
    933      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
    934      */
    935     public interface WifiChangeListener extends ActionListener {
    936         /** indicates that changes were detected in wifi environment
    937          * @param results indicate the access points that exhibited change
    938          */
    939         public void onChanging(ScanResult[] results);           /* changes are found */
    940         /** indicates that no wifi changes are being detected for a while
    941          * @param results indicate the access points that are bing monitored for change
    942          */
    943         public void onQuiescence(ScanResult[] results);         /* changes settled down */
    944     }
    945 
    946     /**
    947      * track changes in wifi environment
    948      * @param listener object to report events on; this object must be unique and must also be
    949      *                 provided on {@link #stopTrackingWifiChange}
    950      */
    951     public void startTrackingWifiChange(WifiChangeListener listener) {
    952         Preconditions.checkNotNull(listener, "listener cannot be null");
    953         int key = addListener(listener);
    954         if (key == INVALID_KEY) return;
    955         validateChannel();
    956         mAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
    957     }
    958 
    959     /**
    960      * stop tracking changes in wifi environment
    961      * @param listener object that was provided to report events on {@link
    962      * #stopTrackingWifiChange}
    963      */
    964     public void stopTrackingWifiChange(WifiChangeListener listener) {
    965         int key = removeListener(listener);
    966         if (key == INVALID_KEY) return;
    967         validateChannel();
    968         mAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
    969     }
    970 
    971     /** @hide */
    972     @SystemApi
    973     public void configureWifiChange(WifiChangeSettings settings) {
    974         validateChannel();
    975         mAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
    976     }
    977 
    978     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
    979     public static interface BssidListener extends ActionListener {
    980         /** indicates that access points were found by on going scans
    981          * @param results list of scan results, one for each access point visible currently
    982          */
    983         public void onFound(ScanResult[] results);
    984         /** indicates that access points were missed by on going scans
    985          * @param results list of scan results, for each access point that is not visible anymore
    986          */
    987         public void onLost(ScanResult[] results);
    988     }
    989 
    990     /** @hide */
    991     @SystemApi
    992     public static class HotlistSettings implements Parcelable {
    993         public BssidInfo[] bssidInfos;
    994         public int apLostThreshold;
    995 
    996         /** Implement the Parcelable interface {@hide} */
    997         public int describeContents() {
    998             return 0;
    999         }
   1000 
   1001         /** Implement the Parcelable interface {@hide} */
   1002         public void writeToParcel(Parcel dest, int flags) {
   1003             dest.writeInt(apLostThreshold);
   1004 
   1005             if (bssidInfos != null) {
   1006                 dest.writeInt(bssidInfos.length);
   1007                 for (int i = 0; i < bssidInfos.length; i++) {
   1008                     BssidInfo info = bssidInfos[i];
   1009                     dest.writeString(info.bssid);
   1010                     dest.writeInt(info.low);
   1011                     dest.writeInt(info.high);
   1012                     dest.writeInt(info.frequencyHint);
   1013                 }
   1014             } else {
   1015                 dest.writeInt(0);
   1016             }
   1017         }
   1018 
   1019         /** Implement the Parcelable interface {@hide} */
   1020         public static final Creator<HotlistSettings> CREATOR =
   1021                 new Creator<HotlistSettings>() {
   1022                     public HotlistSettings createFromParcel(Parcel in) {
   1023                         HotlistSettings settings = new HotlistSettings();
   1024                         settings.apLostThreshold = in.readInt();
   1025                         int n = in.readInt();
   1026                         settings.bssidInfos = new BssidInfo[n];
   1027                         for (int i = 0; i < n; i++) {
   1028                             BssidInfo info = new BssidInfo();
   1029                             info.bssid = in.readString();
   1030                             info.low = in.readInt();
   1031                             info.high = in.readInt();
   1032                             info.frequencyHint = in.readInt();
   1033                             settings.bssidInfos[i] = info;
   1034                         }
   1035                         return settings;
   1036                     }
   1037 
   1038                     public HotlistSettings[] newArray(int size) {
   1039                         return new HotlistSettings[size];
   1040                     }
   1041                 };
   1042     }
   1043 
   1044     /**
   1045      * set interesting access points to find
   1046      * @param bssidInfos access points of interest
   1047      * @param apLostThreshold number of scans needed to indicate that AP is lost
   1048      * @param listener object provided to report events on; this object must be unique and must
   1049      *                 also be provided on {@link #stopTrackingBssids}
   1050      */
   1051     public void startTrackingBssids(BssidInfo[] bssidInfos,
   1052                                     int apLostThreshold, BssidListener listener) {
   1053         Preconditions.checkNotNull(listener, "listener cannot be null");
   1054         int key = addListener(listener);
   1055         if (key == INVALID_KEY) return;
   1056         validateChannel();
   1057         HotlistSettings settings = new HotlistSettings();
   1058         settings.bssidInfos = bssidInfos;
   1059         settings.apLostThreshold = apLostThreshold;
   1060         mAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
   1061     }
   1062 
   1063     /**
   1064      * remove tracking of interesting access points
   1065      * @param listener same object provided in {@link #startTrackingBssids}
   1066      */
   1067     public void stopTrackingBssids(BssidListener listener) {
   1068         Preconditions.checkNotNull(listener, "listener cannot be null");
   1069         int key = removeListener(listener);
   1070         if (key == INVALID_KEY) return;
   1071         validateChannel();
   1072         mAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
   1073     }
   1074 
   1075 
   1076     /* private members and methods */
   1077 
   1078     private static final String TAG = "WifiScanner";
   1079     private static final boolean DBG = false;
   1080 
   1081     /* commands for Wifi Service */
   1082     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
   1083 
   1084     /** @hide */
   1085     public static final int CMD_SCAN                        = BASE + 0;
   1086     /** @hide */
   1087     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
   1088     /** @hide */
   1089     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
   1090     /** @hide */
   1091     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
   1092     /** @hide */
   1093     public static final int CMD_SCAN_RESULT                 = BASE + 5;
   1094     /** @hide */
   1095     public static final int CMD_SET_HOTLIST                 = BASE + 6;
   1096     /** @hide */
   1097     public static final int CMD_RESET_HOTLIST               = BASE + 7;
   1098     /** @hide */
   1099     public static final int CMD_AP_FOUND                    = BASE + 9;
   1100     /** @hide */
   1101     public static final int CMD_AP_LOST                     = BASE + 10;
   1102     /** @hide */
   1103     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
   1104     /** @hide */
   1105     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
   1106     /** @hide */
   1107     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
   1108     /** @hide */
   1109     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
   1110     /** @hide */
   1111     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
   1112     /** @hide */
   1113     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
   1114     /** @hide */
   1115     public static final int CMD_OP_FAILED                   = BASE + 18;
   1116     /** @hide */
   1117     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
   1118     /** @hide */
   1119     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
   1120     /** @hide */
   1121     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
   1122     /** @hide */
   1123     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
   1124     /** @hide */
   1125     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
   1126     /** @hide */
   1127     public static final int CMD_START_PNO_SCAN              = BASE + 24;
   1128     /** @hide */
   1129     public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
   1130     /** @hide */
   1131     public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
   1132 
   1133     private Context mContext;
   1134     private IWifiScanner mService;
   1135 
   1136     private static final int INVALID_KEY = 0;
   1137     private int mListenerKey = 1;
   1138 
   1139     private final SparseArray mListenerMap = new SparseArray();
   1140     private final Object mListenerMapLock = new Object();
   1141 
   1142     private AsyncChannel mAsyncChannel;
   1143     private final Handler mInternalHandler;
   1144 
   1145     /**
   1146      * Create a new WifiScanner instance.
   1147      * Applications will almost always want to use
   1148      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
   1149      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
   1150      * @param context the application context
   1151      * @param service the Binder interface
   1152      * @param looper the Looper used to deliver callbacks
   1153      * @hide
   1154      */
   1155     public WifiScanner(Context context, IWifiScanner service, Looper looper) {
   1156         mContext = context;
   1157         mService = service;
   1158 
   1159         Messenger messenger = null;
   1160         try {
   1161             messenger = mService.getMessenger();
   1162         } catch (RemoteException e) {
   1163             throw e.rethrowFromSystemServer();
   1164         }
   1165 
   1166         if (messenger == null) {
   1167             throw new IllegalStateException("getMessenger() returned null!  This is invalid.");
   1168         }
   1169 
   1170         mAsyncChannel = new AsyncChannel();
   1171 
   1172         mInternalHandler = new ServiceHandler(looper);
   1173         mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
   1174         // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
   1175         // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
   1176         mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
   1177     }
   1178 
   1179     private void validateChannel() {
   1180         if (mAsyncChannel == null) throw new IllegalStateException(
   1181                 "No permission to access and change wifi or a bad initialization");
   1182     }
   1183 
   1184     // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
   1185     // send an error message to internal handler; Otherwise add the listener to the listener map and
   1186     // return the key of the listener.
   1187     private int addListener(ActionListener listener) {
   1188         synchronized (mListenerMapLock) {
   1189             boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
   1190             // Note we need to put the listener into listener map even if it's a duplicate as the
   1191             // internal handler will need the key to find the listener. In case of duplicates,
   1192             // removing duplicate key logic will be handled in internal handler.
   1193             int key = putListener(listener);
   1194             if (keyExists) {
   1195                 if (DBG) Log.d(TAG, "listener key already exists");
   1196                 OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
   1197                         "Outstanding request with same key not stopped yet");
   1198                 Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
   1199                         operationResult);
   1200                 message.sendToTarget();
   1201                 return INVALID_KEY;
   1202             } else {
   1203                 return key;
   1204             }
   1205         }
   1206     }
   1207 
   1208     private int putListener(Object listener) {
   1209         if (listener == null) return INVALID_KEY;
   1210         int key;
   1211         synchronized (mListenerMapLock) {
   1212             do {
   1213                 key = mListenerKey++;
   1214             } while (key == INVALID_KEY);
   1215             mListenerMap.put(key, listener);
   1216         }
   1217         return key;
   1218     }
   1219 
   1220     private Object getListener(int key) {
   1221         if (key == INVALID_KEY) return null;
   1222         synchronized (mListenerMapLock) {
   1223             Object listener = mListenerMap.get(key);
   1224             return listener;
   1225         }
   1226     }
   1227 
   1228     private int getListenerKey(Object listener) {
   1229         if (listener == null) return INVALID_KEY;
   1230         synchronized (mListenerMapLock) {
   1231             int index = mListenerMap.indexOfValue(listener);
   1232             if (index == -1) {
   1233                 return INVALID_KEY;
   1234             } else {
   1235                 return mListenerMap.keyAt(index);
   1236             }
   1237         }
   1238     }
   1239 
   1240     private Object removeListener(int key) {
   1241         if (key == INVALID_KEY) return null;
   1242         synchronized (mListenerMapLock) {
   1243             Object listener = mListenerMap.get(key);
   1244             mListenerMap.remove(key);
   1245             return listener;
   1246         }
   1247     }
   1248 
   1249     private int removeListener(Object listener) {
   1250         int key = getListenerKey(listener);
   1251         if (key == INVALID_KEY) {
   1252             Log.e(TAG, "listener cannot be found");
   1253             return key;
   1254         }
   1255         synchronized (mListenerMapLock) {
   1256             mListenerMap.remove(key);
   1257             return key;
   1258         }
   1259     }
   1260 
   1261     /** @hide */
   1262     public static class OperationResult implements Parcelable {
   1263         public int reason;
   1264         public String description;
   1265 
   1266         public OperationResult(int reason, String description) {
   1267             this.reason = reason;
   1268             this.description = description;
   1269         }
   1270 
   1271         /** Implement the Parcelable interface {@hide} */
   1272         public int describeContents() {
   1273             return 0;
   1274         }
   1275 
   1276         /** Implement the Parcelable interface {@hide} */
   1277         public void writeToParcel(Parcel dest, int flags) {
   1278             dest.writeInt(reason);
   1279             dest.writeString(description);
   1280         }
   1281 
   1282         /** Implement the Parcelable interface {@hide} */
   1283         public static final Creator<OperationResult> CREATOR =
   1284                 new Creator<OperationResult>() {
   1285                     public OperationResult createFromParcel(Parcel in) {
   1286                         int reason = in.readInt();
   1287                         String description = in.readString();
   1288                         return new OperationResult(reason, description);
   1289                     }
   1290 
   1291                     public OperationResult[] newArray(int size) {
   1292                         return new OperationResult[size];
   1293                     }
   1294                 };
   1295     }
   1296 
   1297     private class ServiceHandler extends Handler {
   1298         ServiceHandler(Looper looper) {
   1299             super(looper);
   1300         }
   1301         @Override
   1302         public void handleMessage(Message msg) {
   1303             switch (msg.what) {
   1304                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
   1305                     return;
   1306                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
   1307                     Log.e(TAG, "Channel connection lost");
   1308                     // This will cause all further async API calls on the WifiManager
   1309                     // to fail and throw an exception
   1310                     mAsyncChannel = null;
   1311                     getLooper().quit();
   1312                     return;
   1313             }
   1314 
   1315             Object listener = getListener(msg.arg2);
   1316 
   1317             if (listener == null) {
   1318                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
   1319                 return;
   1320             } else {
   1321                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
   1322             }
   1323 
   1324             switch (msg.what) {
   1325                     /* ActionListeners grouped together */
   1326                 case CMD_OP_SUCCEEDED :
   1327                     ((ActionListener) listener).onSuccess();
   1328                     break;
   1329                 case CMD_OP_FAILED : {
   1330                         OperationResult result = (OperationResult)msg.obj;
   1331                         ((ActionListener) listener).onFailure(result.reason, result.description);
   1332                         removeListener(msg.arg2);
   1333                     }
   1334                     break;
   1335                 case CMD_SCAN_RESULT :
   1336                     ((ScanListener) listener).onResults(
   1337                             ((ParcelableScanData) msg.obj).getResults());
   1338                     return;
   1339                 case CMD_FULL_SCAN_RESULT :
   1340                     ScanResult result = (ScanResult) msg.obj;
   1341                     ((ScanListener) listener).onFullResult(result);
   1342                     return;
   1343                 case CMD_PERIOD_CHANGED:
   1344                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
   1345                     return;
   1346                 case CMD_AP_FOUND:
   1347                     ((BssidListener) listener).onFound(
   1348                             ((ParcelableScanResults) msg.obj).getResults());
   1349                     return;
   1350                 case CMD_AP_LOST:
   1351                     ((BssidListener) listener).onLost(
   1352                             ((ParcelableScanResults) msg.obj).getResults());
   1353                     return;
   1354                 case CMD_WIFI_CHANGE_DETECTED:
   1355                     ((WifiChangeListener) listener).onChanging(
   1356                             ((ParcelableScanResults) msg.obj).getResults());
   1357                    return;
   1358                 case CMD_WIFI_CHANGES_STABILIZED:
   1359                     ((WifiChangeListener) listener).onQuiescence(
   1360                             ((ParcelableScanResults) msg.obj).getResults());
   1361                     return;
   1362                 case CMD_SINGLE_SCAN_COMPLETED:
   1363                     if (DBG) Log.d(TAG, "removing listener for single scan");
   1364                     removeListener(msg.arg2);
   1365                     break;
   1366                 case CMD_PNO_NETWORK_FOUND:
   1367                     ((PnoScanListener) listener).onPnoNetworkFound(
   1368                             ((ParcelableScanResults) msg.obj).getResults());
   1369                     return;
   1370                 default:
   1371                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
   1372                     return;
   1373             }
   1374         }
   1375     }
   1376 }
   1377