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.HandlerThread;
     24 import android.os.Looper;
     25 import android.os.Message;
     26 import android.os.Messenger;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 import android.util.SparseArray;
     32 
     33 import com.android.internal.util.AsyncChannel;
     34 import com.android.internal.util.Protocol;
     35 
     36 import java.util.List;
     37 import java.util.concurrent.CountDownLatch;
     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 
     82     /** @hide */
     83     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
     84 
     85     /**
     86      * Generic action callback invocation interface
     87      *  @hide
     88      */
     89     @SystemApi
     90     public static interface ActionListener {
     91         public void onSuccess();
     92         public void onFailure(int reason, String description);
     93     }
     94 
     95     /**
     96      * gives you all the possible channels; channel is specified as an
     97      * integer with frequency in MHz i.e. channel 1 is 2412
     98      * @hide
     99      */
    100     public List<Integer> getAvailableChannels(int band) {
    101         try {
    102             Bundle bundle =  mService.getAvailableChannels(band);
    103             return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
    104         } catch (RemoteException e) {
    105             return null;
    106         }
    107     }
    108 
    109     /**
    110      * provides channel specification for scanning
    111      */
    112     public static class ChannelSpec {
    113         /**
    114          * channel frequency in MHz; for example channel 1 is specified as 2412
    115          */
    116         public int frequency;
    117         /**
    118          * if true, scan this channel in passive fashion.
    119          * This flag is ignored on DFS channel specification.
    120          * @hide
    121          */
    122         public boolean passive;                                    /* ignored on DFS channels */
    123         /**
    124          * how long to dwell on this channel
    125          * @hide
    126          */
    127         public int dwellTimeMS;                                    /* not supported for now */
    128 
    129         /**
    130          * default constructor for channel spec
    131          */
    132         public ChannelSpec(int frequency) {
    133             this.frequency = frequency;
    134             passive = false;
    135             dwellTimeMS = 0;
    136         }
    137     }
    138 
    139     /** reports {@link ScanListener#onResults} when underlying buffers are full
    140      * @deprecated
    141      */
    142     @Deprecated
    143     public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
    144     /** reports {@link ScanListener#onResults} after each scan */
    145     public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
    146     /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */
    147     public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
    148     /** do not batch */
    149     public static final int REPORT_EVENT_NO_BATCH = 4;
    150 
    151     /**
    152      * scan configuration parameters to be sent to {@link #startBackgroundScan}
    153      */
    154     public static class ScanSettings implements Parcelable {
    155 
    156         /** one of the WIFI_BAND values */
    157         public int band;
    158         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
    159         public ChannelSpec[] channels;
    160         /** period of background scan; in millisecond, 0 => single shot scan */
    161         public int periodInMs;
    162         /** must have a valid REPORT_EVENT value */
    163         public int reportEvents;
    164         /** defines number of bssids to cache from each scan */
    165         public int numBssidsPerScan;
    166         /**
    167          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
    168          * to wake up at fixed interval
    169          */
    170         public int maxScansToCache;
    171 
    172         /** Implement the Parcelable interface {@hide} */
    173         public int describeContents() {
    174             return 0;
    175         }
    176 
    177         /** Implement the Parcelable interface {@hide} */
    178         public void writeToParcel(Parcel dest, int flags) {
    179             dest.writeInt(band);
    180             dest.writeInt(periodInMs);
    181             dest.writeInt(reportEvents);
    182             dest.writeInt(numBssidsPerScan);
    183             dest.writeInt(maxScansToCache);
    184 
    185             if (channels != null) {
    186                 dest.writeInt(channels.length);
    187 
    188                 for (int i = 0; i < channels.length; i++) {
    189                     dest.writeInt(channels[i].frequency);
    190                     dest.writeInt(channels[i].dwellTimeMS);
    191                     dest.writeInt(channels[i].passive ? 1 : 0);
    192                 }
    193             } else {
    194                 dest.writeInt(0);
    195             }
    196         }
    197 
    198         /** Implement the Parcelable interface {@hide} */
    199         public static final Creator<ScanSettings> CREATOR =
    200                 new Creator<ScanSettings>() {
    201                     public ScanSettings createFromParcel(Parcel in) {
    202 
    203                         ScanSettings settings = new ScanSettings();
    204                         settings.band = in.readInt();
    205                         settings.periodInMs = in.readInt();
    206                         settings.reportEvents = in.readInt();
    207                         settings.numBssidsPerScan = in.readInt();
    208                         settings.maxScansToCache = in.readInt();
    209                         int num_channels = in.readInt();
    210                         settings.channels = new ChannelSpec[num_channels];
    211                         for (int i = 0; i < num_channels; i++) {
    212                             int frequency = in.readInt();
    213 
    214                             ChannelSpec spec = new ChannelSpec(frequency);
    215                             spec.dwellTimeMS = in.readInt();
    216                             spec.passive = in.readInt() == 1;
    217                             settings.channels[i] = spec;
    218                         }
    219 
    220                         return settings;
    221                     }
    222 
    223                     public ScanSettings[] newArray(int size) {
    224                         return new ScanSettings[size];
    225                     }
    226                 };
    227 
    228     }
    229 
    230     /**
    231      * all the information garnered from a single scan
    232      */
    233     public static class ScanData implements Parcelable {
    234         /** scan identifier */
    235         private int mId;
    236         /** additional information about scan
    237          * 0 => no special issues encountered in the scan
    238          * non-zero => scan was truncated, so results may not be complete
    239          */
    240         private int mFlags;
    241         /** all scan results discovered in this scan, sorted by timestamp in ascending order */
    242         private ScanResult mResults[];
    243 
    244         ScanData() {}
    245 
    246         public ScanData(int id, int flags, ScanResult[] results) {
    247             mId = id;
    248             mFlags = flags;
    249             mResults = results;
    250         }
    251 
    252         public ScanData(ScanData s) {
    253             mId = s.mId;
    254             mFlags = s.mFlags;
    255             mResults = new ScanResult[s.mResults.length];
    256             for (int i = 0; i < s.mResults.length; i++) {
    257                 ScanResult result = s.mResults[i];
    258                 ScanResult newResult = new ScanResult(result);
    259                 mResults[i] = newResult;
    260             }
    261         }
    262 
    263         public int getId() {
    264             return mId;
    265         }
    266 
    267         public int getFlags() {
    268             return mFlags;
    269         }
    270 
    271         public ScanResult[] getResults() {
    272             return mResults;
    273         }
    274 
    275         /** Implement the Parcelable interface {@hide} */
    276         public int describeContents() {
    277             return 0;
    278         }
    279 
    280         /** Implement the Parcelable interface {@hide} */
    281         public void writeToParcel(Parcel dest, int flags) {
    282             if (mResults != null) {
    283                 dest.writeInt(mId);
    284                 dest.writeInt(mFlags);
    285                 dest.writeInt(mResults.length);
    286                 for (int i = 0; i < mResults.length; i++) {
    287                     ScanResult result = mResults[i];
    288                     result.writeToParcel(dest, flags);
    289                 }
    290             } else {
    291                 dest.writeInt(0);
    292             }
    293         }
    294 
    295         /** Implement the Parcelable interface {@hide} */
    296         public static final Creator<ScanData> CREATOR =
    297                 new Creator<ScanData>() {
    298                     public ScanData createFromParcel(Parcel in) {
    299                         int id = in.readInt();
    300                         int flags = in.readInt();
    301                         int n = in.readInt();
    302                         ScanResult results[] = new ScanResult[n];
    303                         for (int i = 0; i < n; i++) {
    304                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    305                         }
    306                         return new ScanData(id, flags, results);
    307                     }
    308 
    309                     public ScanData[] newArray(int size) {
    310                         return new ScanData[size];
    311                     }
    312                 };
    313     }
    314 
    315     public static class ParcelableScanData implements Parcelable {
    316 
    317         public ScanData mResults[];
    318 
    319         public ParcelableScanData(ScanData[] results) {
    320             mResults = results;
    321         }
    322 
    323         public ScanData[] getResults() {
    324             return mResults;
    325         }
    326 
    327         /** Implement the Parcelable interface {@hide} */
    328         public int describeContents() {
    329             return 0;
    330         }
    331 
    332         /** Implement the Parcelable interface {@hide} */
    333         public void writeToParcel(Parcel dest, int flags) {
    334             if (mResults != null) {
    335                 dest.writeInt(mResults.length);
    336                 for (int i = 0; i < mResults.length; i++) {
    337                     ScanData result = mResults[i];
    338                     result.writeToParcel(dest, flags);
    339                 }
    340             } else {
    341                 dest.writeInt(0);
    342             }
    343         }
    344 
    345         /** Implement the Parcelable interface {@hide} */
    346         public static final Creator<ParcelableScanData> CREATOR =
    347                 new Creator<ParcelableScanData>() {
    348                     public ParcelableScanData createFromParcel(Parcel in) {
    349                         int n = in.readInt();
    350                         ScanData results[] = new ScanData[n];
    351                         for (int i = 0; i < n; i++) {
    352                             results[i] = ScanData.CREATOR.createFromParcel(in);
    353                         }
    354                         return new ParcelableScanData(results);
    355                     }
    356 
    357                     public ParcelableScanData[] newArray(int size) {
    358                         return new ParcelableScanData[size];
    359                     }
    360                 };
    361     }
    362 
    363     public static class ParcelableScanResults implements Parcelable {
    364 
    365         public ScanResult mResults[];
    366 
    367         public ParcelableScanResults(ScanResult[] results) {
    368             mResults = results;
    369         }
    370 
    371         public ScanResult[] getResults() {
    372             return mResults;
    373         }
    374 
    375         /** Implement the Parcelable interface {@hide} */
    376         public int describeContents() {
    377             return 0;
    378         }
    379 
    380         /** Implement the Parcelable interface {@hide} */
    381         public void writeToParcel(Parcel dest, int flags) {
    382             if (mResults != null) {
    383                 dest.writeInt(mResults.length);
    384                 for (int i = 0; i < mResults.length; i++) {
    385                     ScanResult result = mResults[i];
    386                     result.writeToParcel(dest, flags);
    387                 }
    388             } else {
    389                 dest.writeInt(0);
    390             }
    391         }
    392 
    393         /** Implement the Parcelable interface {@hide} */
    394         public static final Creator<ParcelableScanResults> CREATOR =
    395                 new Creator<ParcelableScanResults>() {
    396                     public ParcelableScanResults createFromParcel(Parcel in) {
    397                         int n = in.readInt();
    398                         ScanResult results[] = new ScanResult[n];
    399                         for (int i = 0; i < n; i++) {
    400                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    401                         }
    402                         return new ParcelableScanResults(results);
    403                     }
    404 
    405                     public ParcelableScanResults[] newArray(int size) {
    406                         return new ParcelableScanResults[size];
    407                     }
    408                 };
    409     }
    410 
    411     /**
    412      * interface to get scan events on; specify this on {@link #startBackgroundScan} or
    413      * {@link #startScan}
    414      */
    415     public interface ScanListener extends ActionListener {
    416         /**
    417          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
    418          * same period requested. If period of a scan is changed; it is reported by this event.
    419          */
    420         public void onPeriodChanged(int periodInMs);
    421         /**
    422          * reports results retrieved from background scan and single shot scans
    423          */
    424         public void onResults(ScanData[] results);
    425         /**
    426          * reports full scan result for each access point found in scan
    427          */
    428         public void onFullResult(ScanResult fullScanResult);
    429     }
    430 
    431     /** start wifi scan in background
    432      * @param settings specifies various parameters for the scan; for more information look at
    433      * {@link ScanSettings}
    434      * @param listener specifies the object to report events to. This object is also treated as a
    435      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    436      *                 scans should also not share this object.
    437      */
    438     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
    439         validateChannel();
    440         sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
    441     }
    442     /**
    443      * stop an ongoing wifi scan
    444      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    445      *  #startBackgroundScan}
    446      */
    447     public void stopBackgroundScan(ScanListener listener) {
    448         validateChannel();
    449         sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
    450     }
    451     /**
    452      * reports currently available scan results on appropriate listeners
    453      * @return true if all scan results were reported correctly
    454      */
    455     public boolean getScanResults() {
    456         validateChannel();
    457         Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
    458         return reply.what == CMD_OP_SUCCEEDED;
    459     }
    460 
    461     /**
    462      * starts a single scan and reports results asynchronously
    463      * @param settings specifies various parameters for the scan; for more information look at
    464      * {@link ScanSettings}
    465      * @param listener specifies the object to report events to. This object is also treated as a
    466      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    467      *                 scans should also not share this object.
    468      */
    469     public void startScan(ScanSettings settings, ScanListener listener) {
    470         validateChannel();
    471         sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings);
    472     }
    473 
    474     /**
    475      * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
    476      * hasn't been called on the listener, ignored otherwise
    477      * @param listener
    478      */
    479     public void stopScan(ScanListener listener) {
    480         validateChannel();
    481         sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener));
    482     }
    483 
    484     /** specifies information about an access point of interest */
    485     public static class BssidInfo {
    486         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
    487         public String bssid;
    488         /** low signal strength threshold; more information at {@link ScanResult#level} */
    489         public int low;                                            /* minimum RSSI */
    490         /** high signal threshold; more information at {@link ScanResult#level} */
    491         public int high;                                           /* maximum RSSI */
    492         /** channel frequency (in KHz) where you may find this BSSID */
    493         public int frequencyHint;
    494     }
    495 
    496     /** @hide */
    497     @SystemApi
    498     public static class WifiChangeSettings implements Parcelable {
    499         public int rssiSampleSize;                          /* sample size for RSSI averaging */
    500         public int lostApSampleSize;                        /* samples to confirm AP's loss */
    501         public int unchangedSampleSize;                     /* samples to confirm no change */
    502         public int minApsBreachingThreshold;                /* change threshold to trigger event */
    503         public int periodInMs;                              /* scan period in millisecond */
    504         public BssidInfo[] bssidInfos;
    505 
    506         /** Implement the Parcelable interface {@hide} */
    507         public int describeContents() {
    508             return 0;
    509         }
    510 
    511         /** Implement the Parcelable interface {@hide} */
    512         public void writeToParcel(Parcel dest, int flags) {
    513             dest.writeInt(rssiSampleSize);
    514             dest.writeInt(lostApSampleSize);
    515             dest.writeInt(unchangedSampleSize);
    516             dest.writeInt(minApsBreachingThreshold);
    517             dest.writeInt(periodInMs);
    518             if (bssidInfos != null) {
    519                 dest.writeInt(bssidInfos.length);
    520                 for (int i = 0; i < bssidInfos.length; i++) {
    521                     BssidInfo info = bssidInfos[i];
    522                     dest.writeString(info.bssid);
    523                     dest.writeInt(info.low);
    524                     dest.writeInt(info.high);
    525                     dest.writeInt(info.frequencyHint);
    526                 }
    527             } else {
    528                 dest.writeInt(0);
    529             }
    530         }
    531 
    532         /** Implement the Parcelable interface {@hide} */
    533         public static final Creator<WifiChangeSettings> CREATOR =
    534                 new Creator<WifiChangeSettings>() {
    535                     public WifiChangeSettings createFromParcel(Parcel in) {
    536                         WifiChangeSettings settings = new WifiChangeSettings();
    537                         settings.rssiSampleSize = in.readInt();
    538                         settings.lostApSampleSize = in.readInt();
    539                         settings.unchangedSampleSize = in.readInt();
    540                         settings.minApsBreachingThreshold = in.readInt();
    541                         settings.periodInMs = in.readInt();
    542                         int len = in.readInt();
    543                         settings.bssidInfos = new BssidInfo[len];
    544                         for (int i = 0; i < len; i++) {
    545                             BssidInfo info = new BssidInfo();
    546                             info.bssid = in.readString();
    547                             info.low = in.readInt();
    548                             info.high = in.readInt();
    549                             info.frequencyHint = in.readInt();
    550                             settings.bssidInfos[i] = info;
    551                         }
    552                         return settings;
    553                     }
    554 
    555                     public WifiChangeSettings[] newArray(int size) {
    556                         return new WifiChangeSettings[size];
    557                     }
    558                 };
    559 
    560     }
    561 
    562     /** configure WifiChange detection
    563      * @param rssiSampleSize number of samples used for RSSI averaging
    564      * @param lostApSampleSize number of samples to confirm an access point's loss
    565      * @param unchangedSampleSize number of samples to confirm there are no changes
    566      * @param minApsBreachingThreshold minimum number of access points that need to be
    567      *                                 out of range to detect WifiChange
    568      * @param periodInMs indicates period of scan to find changes
    569      * @param bssidInfos access points to watch
    570      */
    571     public void configureWifiChange(
    572             int rssiSampleSize,                             /* sample size for RSSI averaging */
    573             int lostApSampleSize,                           /* samples to confirm AP's loss */
    574             int unchangedSampleSize,                        /* samples to confirm no change */
    575             int minApsBreachingThreshold,                   /* change threshold to trigger event */
    576             int periodInMs,                                 /* period of scan */
    577             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
    578             )
    579     {
    580         validateChannel();
    581 
    582         WifiChangeSettings settings = new WifiChangeSettings();
    583         settings.rssiSampleSize = rssiSampleSize;
    584         settings.lostApSampleSize = lostApSampleSize;
    585         settings.unchangedSampleSize = unchangedSampleSize;
    586         settings.minApsBreachingThreshold = minApsBreachingThreshold;
    587         settings.periodInMs = periodInMs;
    588         settings.bssidInfos = bssidInfos;
    589 
    590         configureWifiChange(settings);
    591     }
    592 
    593     /**
    594      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
    595      */
    596     public interface WifiChangeListener extends ActionListener {
    597         /** indicates that changes were detected in wifi environment
    598          * @param results indicate the access points that exhibited change
    599          */
    600         public void onChanging(ScanResult[] results);           /* changes are found */
    601         /** indicates that no wifi changes are being detected for a while
    602          * @param results indicate the access points that are bing monitored for change
    603          */
    604         public void onQuiescence(ScanResult[] results);         /* changes settled down */
    605     }
    606 
    607     /**
    608      * track changes in wifi environment
    609      * @param listener object to report events on; this object must be unique and must also be
    610      *                 provided on {@link #stopTrackingWifiChange}
    611      */
    612     public void startTrackingWifiChange(WifiChangeListener listener) {
    613         validateChannel();
    614         sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
    615     }
    616 
    617     /**
    618      * stop tracking changes in wifi environment
    619      * @param listener object that was provided to report events on {@link
    620      * #stopTrackingWifiChange}
    621      */
    622     public void stopTrackingWifiChange(WifiChangeListener listener) {
    623         validateChannel();
    624         sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
    625     }
    626 
    627     /** @hide */
    628     @SystemApi
    629     public void configureWifiChange(WifiChangeSettings settings) {
    630         validateChannel();
    631         sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
    632     }
    633 
    634     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
    635     public static interface BssidListener extends ActionListener {
    636         /** indicates that access points were found by on going scans
    637          * @param results list of scan results, one for each access point visible currently
    638          */
    639         public void onFound(ScanResult[] results);
    640         /** indicates that access points were missed by on going scans
    641          * @param results list of scan results, for each access point that is not visible anymore
    642          */
    643         public void onLost(ScanResult[] results);
    644     }
    645 
    646     /** @hide */
    647     @SystemApi
    648     public static class HotlistSettings implements Parcelable {
    649         public BssidInfo[] bssidInfos;
    650         public int apLostThreshold;
    651 
    652         /** Implement the Parcelable interface {@hide} */
    653         public int describeContents() {
    654             return 0;
    655         }
    656 
    657         /** Implement the Parcelable interface {@hide} */
    658         public void writeToParcel(Parcel dest, int flags) {
    659             dest.writeInt(apLostThreshold);
    660 
    661             if (bssidInfos != null) {
    662                 dest.writeInt(bssidInfos.length);
    663                 for (int i = 0; i < bssidInfos.length; i++) {
    664                     BssidInfo info = bssidInfos[i];
    665                     dest.writeString(info.bssid);
    666                     dest.writeInt(info.low);
    667                     dest.writeInt(info.high);
    668                     dest.writeInt(info.frequencyHint);
    669                 }
    670             } else {
    671                 dest.writeInt(0);
    672             }
    673         }
    674 
    675         /** Implement the Parcelable interface {@hide} */
    676         public static final Creator<HotlistSettings> CREATOR =
    677                 new Creator<HotlistSettings>() {
    678                     public HotlistSettings createFromParcel(Parcel in) {
    679                         HotlistSettings settings = new HotlistSettings();
    680                         settings.apLostThreshold = in.readInt();
    681                         int n = in.readInt();
    682                         settings.bssidInfos = new BssidInfo[n];
    683                         for (int i = 0; i < n; i++) {
    684                             BssidInfo info = new BssidInfo();
    685                             info.bssid = in.readString();
    686                             info.low = in.readInt();
    687                             info.high = in.readInt();
    688                             info.frequencyHint = in.readInt();
    689                             settings.bssidInfos[i] = info;
    690                         }
    691                         return settings;
    692                     }
    693 
    694                     public HotlistSettings[] newArray(int size) {
    695                         return new HotlistSettings[size];
    696                     }
    697                 };
    698     }
    699 
    700     /**
    701      * set interesting access points to find
    702      * @param bssidInfos access points of interest
    703      * @param apLostThreshold number of scans needed to indicate that AP is lost
    704      * @param listener object provided to report events on; this object must be unique and must
    705      *                 also be provided on {@link #stopTrackingBssids}
    706      */
    707     public void startTrackingBssids(BssidInfo[] bssidInfos,
    708                                     int apLostThreshold, BssidListener listener) {
    709         validateChannel();
    710         HotlistSettings settings = new HotlistSettings();
    711         settings.bssidInfos = bssidInfos;
    712         sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
    713     }
    714 
    715     /**
    716      * remove tracking of interesting access points
    717      * @param listener same object provided in {@link #startTrackingBssids}
    718      */
    719     public void stopTrackingBssids(BssidListener listener) {
    720         validateChannel();
    721         sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
    722     }
    723 
    724 
    725     /* private members and methods */
    726 
    727     private static final String TAG = "WifiScanner";
    728     private static final boolean DBG = false;
    729 
    730     /* commands for Wifi Service */
    731     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
    732 
    733     /** @hide */
    734     public static final int CMD_SCAN                        = BASE + 0;
    735     /** @hide */
    736     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
    737     /** @hide */
    738     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
    739     /** @hide */
    740     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
    741     /** @hide */
    742     public static final int CMD_SCAN_RESULT                 = BASE + 5;
    743     /** @hide */
    744     public static final int CMD_SET_HOTLIST                 = BASE + 6;
    745     /** @hide */
    746     public static final int CMD_RESET_HOTLIST               = BASE + 7;
    747     /** @hide */
    748     public static final int CMD_AP_FOUND                    = BASE + 9;
    749     /** @hide */
    750     public static final int CMD_AP_LOST                     = BASE + 10;
    751     /** @hide */
    752     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
    753     /** @hide */
    754     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
    755     /** @hide */
    756     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
    757     /** @hide */
    758     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
    759     /** @hide */
    760     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
    761     /** @hide */
    762     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
    763     /** @hide */
    764     public static final int CMD_OP_FAILED                   = BASE + 18;
    765     /** @hide */
    766     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
    767     /** @hide */
    768     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
    769     /** @hide */
    770     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
    771     /** @hide */
    772     public static final int CMD_STOP_SINGLE_SCAN            = BASE + 22;
    773     /** @hide */
    774     public static final int CMD_SINGLE_SCAN_COMPLETED       = BASE + 23;
    775 
    776     private Context mContext;
    777     private IWifiScanner mService;
    778 
    779     private static final int INVALID_KEY = 0;
    780     private static int sListenerKey = 1;
    781 
    782     private static final SparseArray sListenerMap = new SparseArray();
    783     private static final Object sListenerMapLock = new Object();
    784 
    785     private static AsyncChannel sAsyncChannel;
    786     private static CountDownLatch sConnected;
    787 
    788     private static final Object sThreadRefLock = new Object();
    789     private static int sThreadRefCount;
    790     private static HandlerThread sHandlerThread;
    791 
    792     /**
    793      * Create a new WifiScanner instance.
    794      * Applications will almost always want to use
    795      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
    796      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
    797      * @param context the application context
    798      * @param service the Binder interface
    799      * @hide
    800      */
    801     public WifiScanner(Context context, IWifiScanner service) {
    802         mContext = context;
    803         mService = service;
    804         init();
    805     }
    806 
    807     private void init() {
    808         synchronized (sThreadRefLock) {
    809             if (++sThreadRefCount == 1) {
    810                 Messenger messenger = null;
    811                 try {
    812                     messenger = mService.getMessenger();
    813                 } catch (RemoteException e) {
    814                     /* do nothing */
    815                 } catch (SecurityException e) {
    816                     /* do nothing */
    817                 }
    818 
    819                 if (messenger == null) {
    820                     sAsyncChannel = null;
    821                     return;
    822                 }
    823 
    824                 sHandlerThread = new HandlerThread("WifiScanner");
    825                 sAsyncChannel = new AsyncChannel();
    826                 sConnected = new CountDownLatch(1);
    827 
    828                 sHandlerThread.start();
    829                 Handler handler = new ServiceHandler(sHandlerThread.getLooper());
    830                 sAsyncChannel.connect(mContext, handler, messenger);
    831                 try {
    832                     sConnected.await();
    833                 } catch (InterruptedException e) {
    834                     Log.e(TAG, "interrupted wait at init");
    835                 }
    836             }
    837         }
    838     }
    839 
    840     private void validateChannel() {
    841         if (sAsyncChannel == null) throw new IllegalStateException(
    842                 "No permission to access and change wifi or a bad initialization");
    843     }
    844 
    845     private static int putListener(Object listener) {
    846         if (listener == null) return INVALID_KEY;
    847         int key;
    848         synchronized (sListenerMapLock) {
    849             do {
    850                 key = sListenerKey++;
    851             } while (key == INVALID_KEY);
    852             sListenerMap.put(key, listener);
    853         }
    854         return key;
    855     }
    856 
    857     private static Object getListener(int key) {
    858         if (key == INVALID_KEY) return null;
    859         synchronized (sListenerMapLock) {
    860             Object listener = sListenerMap.get(key);
    861             return listener;
    862         }
    863     }
    864 
    865     private static int getListenerKey(Object listener) {
    866         if (listener == null) return INVALID_KEY;
    867         synchronized (sListenerMapLock) {
    868             int index = sListenerMap.indexOfValue(listener);
    869             if (index == -1) {
    870                 return INVALID_KEY;
    871             } else {
    872                 return sListenerMap.keyAt(index);
    873             }
    874         }
    875     }
    876 
    877     private static Object removeListener(int key) {
    878         if (key == INVALID_KEY) return null;
    879         synchronized (sListenerMapLock) {
    880             Object listener = sListenerMap.get(key);
    881             sListenerMap.remove(key);
    882             return listener;
    883         }
    884     }
    885 
    886     private static int removeListener(Object listener) {
    887         int key = getListenerKey(listener);
    888         if (key == INVALID_KEY) return key;
    889         synchronized (sListenerMapLock) {
    890             sListenerMap.remove(key);
    891             return key;
    892         }
    893     }
    894 
    895     /** @hide */
    896     public static class OperationResult implements Parcelable {
    897         public int reason;
    898         public String description;
    899 
    900         public OperationResult(int reason, String description) {
    901             this.reason = reason;
    902             this.description = description;
    903         }
    904 
    905         /** Implement the Parcelable interface {@hide} */
    906         public int describeContents() {
    907             return 0;
    908         }
    909 
    910         /** Implement the Parcelable interface {@hide} */
    911         public void writeToParcel(Parcel dest, int flags) {
    912             dest.writeInt(reason);
    913             dest.writeString(description);
    914         }
    915 
    916         /** Implement the Parcelable interface {@hide} */
    917         public static final Creator<OperationResult> CREATOR =
    918                 new Creator<OperationResult>() {
    919                     public OperationResult createFromParcel(Parcel in) {
    920                         int reason = in.readInt();
    921                         String description = in.readString();
    922                         return new OperationResult(reason, description);
    923                     }
    924 
    925                     public OperationResult[] newArray(int size) {
    926                         return new OperationResult[size];
    927                     }
    928                 };
    929     }
    930 
    931     private static class ServiceHandler extends Handler {
    932         ServiceHandler(Looper looper) {
    933             super(looper);
    934         }
    935         @Override
    936         public void handleMessage(Message msg) {
    937             switch (msg.what) {
    938                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
    939                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
    940                         sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
    941                     } else {
    942                         Log.e(TAG, "Failed to set up channel connection");
    943                         // This will cause all further async API calls on the WifiManager
    944                         // to fail and throw an exception
    945                         sAsyncChannel = null;
    946                     }
    947                     sConnected.countDown();
    948                     return;
    949                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
    950                     return;
    951                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
    952                     Log.e(TAG, "Channel connection lost");
    953                     // This will cause all further async API calls on the WifiManager
    954                     // to fail and throw an exception
    955                     sAsyncChannel = null;
    956                     getLooper().quit();
    957                     return;
    958             }
    959 
    960             Object listener = getListener(msg.arg2);
    961 
    962             if (listener == null) {
    963                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
    964                 return;
    965             } else {
    966                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
    967             }
    968 
    969             switch (msg.what) {
    970                     /* ActionListeners grouped together */
    971                 case CMD_OP_SUCCEEDED :
    972                     ((ActionListener) listener).onSuccess();
    973                     break;
    974                 case CMD_OP_FAILED : {
    975                         OperationResult result = (OperationResult)msg.obj;
    976                         ((ActionListener) listener).onFailure(result.reason, result.description);
    977                         removeListener(msg.arg2);
    978                     }
    979                     break;
    980                 case CMD_SCAN_RESULT :
    981                     ((ScanListener) listener).onResults(
    982                             ((ParcelableScanData) msg.obj).getResults());
    983                     return;
    984                 case CMD_FULL_SCAN_RESULT :
    985                     ScanResult result = (ScanResult) msg.obj;
    986                     ((ScanListener) listener).onFullResult(result);
    987                     return;
    988                 case CMD_PERIOD_CHANGED:
    989                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
    990                     return;
    991                 case CMD_AP_FOUND:
    992                     ((BssidListener) listener).onFound(
    993                             ((ParcelableScanResults) msg.obj).getResults());
    994                     return;
    995                 case CMD_AP_LOST:
    996                     ((BssidListener) listener).onLost(
    997                             ((ParcelableScanResults) msg.obj).getResults());
    998                     return;
    999                 case CMD_WIFI_CHANGE_DETECTED:
   1000                     ((WifiChangeListener) listener).onChanging(
   1001                             ((ParcelableScanResults) msg.obj).getResults());
   1002                    return;
   1003                 case CMD_WIFI_CHANGES_STABILIZED:
   1004                     ((WifiChangeListener) listener).onQuiescence(
   1005                             ((ParcelableScanResults) msg.obj).getResults());
   1006                     return;
   1007                 case CMD_SINGLE_SCAN_COMPLETED:
   1008                     if (DBG) Log.d(TAG, "removing listener for single scan");
   1009                     removeListener(msg.arg2);
   1010                     break;
   1011                 default:
   1012                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
   1013                     return;
   1014             }
   1015         }
   1016     }
   1017 }
   1018