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     public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
    141     /** reports {@link ScanListener#onResults} after each scan */
    142     public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1;
    143     /** reports {@link ScanListener#onFullResult} whenever each beacon is discovered */
    144     public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2;
    145 
    146     /**
    147      * scan configuration parameters to be sent to {@link #startBackgroundScan}
    148      */
    149     public static class ScanSettings implements Parcelable {
    150 
    151         /** one of the WIFI_BAND values */
    152         public int band;
    153         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
    154         public ChannelSpec[] channels;
    155         /** period of background scan; in millisecond, 0 => single shot scan */
    156         public int periodInMs;
    157         /** must have a valid REPORT_EVENT value */
    158         public int reportEvents;
    159         /** defines number of bssids to cache from each scan */
    160         public int numBssidsPerScan;
    161 
    162         /** Implement the Parcelable interface {@hide} */
    163         public int describeContents() {
    164             return 0;
    165         }
    166 
    167         /** Implement the Parcelable interface {@hide} */
    168         public void writeToParcel(Parcel dest, int flags) {
    169             dest.writeInt(band);
    170             dest.writeInt(periodInMs);
    171             dest.writeInt(reportEvents);
    172             dest.writeInt(numBssidsPerScan);
    173 
    174             if (channels != null) {
    175                 dest.writeInt(channels.length);
    176 
    177                 for (int i = 0; i < channels.length; i++) {
    178                     dest.writeInt(channels[i].frequency);
    179                     dest.writeInt(channels[i].dwellTimeMS);
    180                     dest.writeInt(channels[i].passive ? 1 : 0);
    181                 }
    182             } else {
    183                 dest.writeInt(0);
    184             }
    185         }
    186 
    187         /** Implement the Parcelable interface {@hide} */
    188         public static final Creator<ScanSettings> CREATOR =
    189                 new Creator<ScanSettings>() {
    190                     public ScanSettings createFromParcel(Parcel in) {
    191 
    192                         ScanSettings settings = new ScanSettings();
    193                         settings.band = in.readInt();
    194                         settings.periodInMs = in.readInt();
    195                         settings.reportEvents = in.readInt();
    196                         settings.numBssidsPerScan = in.readInt();
    197                         int num_channels = in.readInt();
    198                         settings.channels = new ChannelSpec[num_channels];
    199                         for (int i = 0; i < num_channels; i++) {
    200                             int frequency = in.readInt();
    201 
    202                             ChannelSpec spec = new ChannelSpec(frequency);
    203                             spec.dwellTimeMS = in.readInt();
    204                             spec.passive = in.readInt() == 1;
    205                             settings.channels[i] = spec;
    206                         }
    207 
    208                         return settings;
    209                     }
    210 
    211                     public ScanSettings[] newArray(int size) {
    212                         return new ScanSettings[size];
    213                     }
    214                 };
    215 
    216     }
    217 
    218     /** @hide */
    219     public static class ParcelableScanResults implements Parcelable {
    220         public ScanResult mResults[];
    221 
    222         public ParcelableScanResults(ScanResult[] results) {
    223             mResults = results;
    224         }
    225 
    226         public ScanResult[] getResults() {
    227             return mResults;
    228         }
    229 
    230         /** Implement the Parcelable interface {@hide} */
    231         public int describeContents() {
    232             return 0;
    233         }
    234 
    235         /** Implement the Parcelable interface {@hide} */
    236         public void writeToParcel(Parcel dest, int flags) {
    237             if (mResults != null) {
    238                 dest.writeInt(mResults.length);
    239                 for (int i = 0; i < mResults.length; i++) {
    240                     ScanResult result = mResults[i];
    241                     result.writeToParcel(dest, flags);
    242                 }
    243             } else {
    244                 dest.writeInt(0);
    245             }
    246         }
    247 
    248         /** Implement the Parcelable interface {@hide} */
    249         public static final Creator<ParcelableScanResults> CREATOR =
    250                 new Creator<ParcelableScanResults>() {
    251                     public ParcelableScanResults createFromParcel(Parcel in) {
    252                         int n = in.readInt();
    253                         ScanResult results[] = new ScanResult[n];
    254                         for (int i = 0; i < n; i++) {
    255                             results[i] = ScanResult.CREATOR.createFromParcel(in);
    256                         }
    257                         return new ParcelableScanResults(results);
    258                     }
    259 
    260                     public ParcelableScanResults[] newArray(int size) {
    261                         return new ParcelableScanResults[size];
    262                     }
    263                 };
    264     }
    265 
    266     /**
    267      * interface to get scan events on; specify this on {@link #startBackgroundScan}
    268      */
    269     public interface ScanListener extends ActionListener {
    270         /**
    271          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
    272          * same period requested. If period of a scan is changed; it is reported by this event.
    273          */
    274         public void onPeriodChanged(int periodInMs);
    275         /**
    276          * reports results retrieved from background scan
    277          */
    278         public void onResults(ScanResult[] results);
    279         /**
    280          * reports full scan result for each access point found in scan
    281          */
    282         public void onFullResult(ScanResult fullScanResult);
    283     }
    284 
    285     /** start wifi scan in background
    286      * @param settings specifies various parameters for the scan; for more information look at
    287      * {@link ScanSettings}
    288      * @param listener specifies the object to report events to. This object is also treated as a
    289      *                 key for this scan, and must also be specified to cancel the scan. Multiple
    290      *                 scans should also not share this object.
    291      */
    292     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
    293         validateChannel();
    294         sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
    295     }
    296     /**
    297      * stop an ongoing wifi scan
    298      * @param listener specifies which scan to cancel; must be same object as passed in {@link
    299      *  #startBackgroundScan}
    300      */
    301     public void stopBackgroundScan(ScanListener listener) {
    302         validateChannel();
    303         sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
    304     }
    305     /**
    306      * retrieves currently available scan results
    307      */
    308     public ScanResult[] getScanResults() {
    309         validateChannel();
    310         Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
    311         ScanResult[] results = (ScanResult[]) reply.obj;
    312         return results;
    313     }
    314 
    315     /** specifies information about an access point of interest */
    316     public static class BssidInfo {
    317         /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
    318         public String bssid;
    319         /** low signal strength threshold; more information at {@link ScanResult#level} */
    320         public int low;                                            /* minimum RSSI */
    321         /** high signal threshold; more information at {@link ScanResult#level} */
    322         public int high;                                           /* maximum RSSI */
    323         /** channel frequency (in KHz) where you may find this BSSID */
    324         public int frequencyHint;
    325     }
    326 
    327     /** @hide */
    328     @SystemApi
    329     public static class WifiChangeSettings implements Parcelable {
    330         public int rssiSampleSize;                          /* sample size for RSSI averaging */
    331         public int lostApSampleSize;                        /* samples to confirm AP's loss */
    332         public int unchangedSampleSize;                     /* samples to confirm no change */
    333         public int minApsBreachingThreshold;                /* change threshold to trigger event */
    334         public int periodInMs;                              /* scan period in millisecond */
    335         public BssidInfo[] bssidInfos;
    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             dest.writeInt(rssiSampleSize);
    345             dest.writeInt(lostApSampleSize);
    346             dest.writeInt(unchangedSampleSize);
    347             dest.writeInt(minApsBreachingThreshold);
    348             dest.writeInt(periodInMs);
    349             if (bssidInfos != null) {
    350                 dest.writeInt(bssidInfos.length);
    351                 for (int i = 0; i < bssidInfos.length; i++) {
    352                     BssidInfo info = bssidInfos[i];
    353                     dest.writeString(info.bssid);
    354                     dest.writeInt(info.low);
    355                     dest.writeInt(info.high);
    356                     dest.writeInt(info.frequencyHint);
    357                 }
    358             } else {
    359                 dest.writeInt(0);
    360             }
    361         }
    362 
    363         /** Implement the Parcelable interface {@hide} */
    364         public static final Creator<WifiChangeSettings> CREATOR =
    365                 new Creator<WifiChangeSettings>() {
    366                     public WifiChangeSettings createFromParcel(Parcel in) {
    367                         WifiChangeSettings settings = new WifiChangeSettings();
    368                         settings.rssiSampleSize = in.readInt();
    369                         settings.lostApSampleSize = in.readInt();
    370                         settings.unchangedSampleSize = in.readInt();
    371                         settings.minApsBreachingThreshold = in.readInt();
    372                         settings.periodInMs = in.readInt();
    373                         int len = in.readInt();
    374                         settings.bssidInfos = new BssidInfo[len];
    375                         for (int i = 0; i < len; i++) {
    376                             BssidInfo info = new BssidInfo();
    377                             info.bssid = in.readString();
    378                             info.low = in.readInt();
    379                             info.high = in.readInt();
    380                             info.frequencyHint = in.readInt();
    381                             settings.bssidInfos[i] = info;
    382                         }
    383                         return settings;
    384                     }
    385 
    386                     public WifiChangeSettings[] newArray(int size) {
    387                         return new WifiChangeSettings[size];
    388                     }
    389                 };
    390 
    391     }
    392 
    393     /** configure WifiChange detection
    394      * @param rssiSampleSize number of samples used for RSSI averaging
    395      * @param lostApSampleSize number of samples to confirm an access point's loss
    396      * @param unchangedSampleSize number of samples to confirm there are no changes
    397      * @param minApsBreachingThreshold minimum number of access points that need to be
    398      *                                 out of range to detect WifiChange
    399      * @param periodInMs indicates period of scan to find changes
    400      * @param bssidInfos access points to watch
    401      */
    402     public void configureWifiChange(
    403             int rssiSampleSize,                             /* sample size for RSSI averaging */
    404             int lostApSampleSize,                           /* samples to confirm AP's loss */
    405             int unchangedSampleSize,                        /* samples to confirm no change */
    406             int minApsBreachingThreshold,                   /* change threshold to trigger event */
    407             int periodInMs,                                 /* period of scan */
    408             BssidInfo[] bssidInfos                          /* signal thresholds to crosss */
    409             )
    410     {
    411         validateChannel();
    412 
    413         WifiChangeSettings settings = new WifiChangeSettings();
    414         settings.rssiSampleSize = rssiSampleSize;
    415         settings.lostApSampleSize = lostApSampleSize;
    416         settings.unchangedSampleSize = unchangedSampleSize;
    417         settings.minApsBreachingThreshold = minApsBreachingThreshold;
    418         settings.periodInMs = periodInMs;
    419         settings.bssidInfos = bssidInfos;
    420 
    421         configureWifiChange(settings);
    422     }
    423 
    424     /**
    425      * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
    426      */
    427     public interface WifiChangeListener extends ActionListener {
    428         /** indicates that changes were detected in wifi environment
    429          * @param results indicate the access points that exhibited change
    430          */
    431         public void onChanging(ScanResult[] results);           /* changes are found */
    432         /** indicates that no wifi changes are being detected for a while
    433          * @param results indicate the access points that are bing monitored for change
    434          */
    435         public void onQuiescence(ScanResult[] results);         /* changes settled down */
    436     }
    437 
    438     /**
    439      * track changes in wifi environment
    440      * @param listener object to report events on; this object must be unique and must also be
    441      *                 provided on {@link #stopTrackingWifiChange}
    442      */
    443     public void startTrackingWifiChange(WifiChangeListener listener) {
    444         validateChannel();
    445         sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
    446     }
    447 
    448     /**
    449      * stop tracking changes in wifi environment
    450      * @param listener object that was provided to report events on {@link
    451      * #stopTrackingWifiChange}
    452      */
    453     public void stopTrackingWifiChange(WifiChangeListener listener) {
    454         validateChannel();
    455         sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
    456     }
    457 
    458     /** @hide */
    459     @SystemApi
    460     public void configureWifiChange(WifiChangeSettings settings) {
    461         validateChannel();
    462         sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
    463     }
    464 
    465     /** interface to receive hotlist events on; use this on {@link #setHotlist} */
    466     public static interface BssidListener extends ActionListener {
    467         /** indicates that access points were found by on going scans
    468          * @param results list of scan results, one for each access point visible currently
    469          */
    470         public void onFound(ScanResult[] results);
    471     }
    472 
    473     /** @hide */
    474     @SystemApi
    475     public static class HotlistSettings implements Parcelable {
    476         public BssidInfo[] bssidInfos;
    477         public int apLostThreshold;
    478 
    479         /** Implement the Parcelable interface {@hide} */
    480         public int describeContents() {
    481             return 0;
    482         }
    483 
    484         /** Implement the Parcelable interface {@hide} */
    485         public void writeToParcel(Parcel dest, int flags) {
    486             dest.writeInt(apLostThreshold);
    487 
    488             if (bssidInfos != null) {
    489                 dest.writeInt(bssidInfos.length);
    490                 for (int i = 0; i < bssidInfos.length; i++) {
    491                     BssidInfo info = bssidInfos[i];
    492                     dest.writeString(info.bssid);
    493                     dest.writeInt(info.low);
    494                     dest.writeInt(info.high);
    495                     dest.writeInt(info.frequencyHint);
    496                 }
    497             } else {
    498                 dest.writeInt(0);
    499             }
    500         }
    501 
    502         /** Implement the Parcelable interface {@hide} */
    503         public static final Creator<HotlistSettings> CREATOR =
    504                 new Creator<HotlistSettings>() {
    505                     public HotlistSettings createFromParcel(Parcel in) {
    506                         HotlistSettings settings = new HotlistSettings();
    507                         settings.apLostThreshold = in.readInt();
    508                         int n = in.readInt();
    509                         settings.bssidInfos = new BssidInfo[n];
    510                         for (int i = 0; i < n; i++) {
    511                             BssidInfo info = new BssidInfo();
    512                             info.bssid = in.readString();
    513                             info.low = in.readInt();
    514                             info.high = in.readInt();
    515                             info.frequencyHint = in.readInt();
    516                             settings.bssidInfos[i] = info;
    517                         }
    518                         return settings;
    519                     }
    520 
    521                     public HotlistSettings[] newArray(int size) {
    522                         return new HotlistSettings[size];
    523                     }
    524                 };
    525     }
    526 
    527     /**
    528      * set interesting access points to find
    529      * @param bssidInfos access points of interest
    530      * @param apLostThreshold number of scans needed to indicate that AP is lost
    531      * @param listener object provided to report events on; this object must be unique and must
    532      *                 also be provided on {@link #stopTrackingBssids}
    533      */
    534     public void startTrackingBssids(BssidInfo[] bssidInfos,
    535                                     int apLostThreshold, BssidListener listener) {
    536         validateChannel();
    537         HotlistSettings settings = new HotlistSettings();
    538         settings.bssidInfos = bssidInfos;
    539         sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
    540     }
    541 
    542     /**
    543      * remove tracking of interesting access points
    544      * @param listener same object provided in {@link #startTrackingBssids}
    545      */
    546     public void stopTrackingBssids(BssidListener listener) {
    547         validateChannel();
    548         sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
    549     }
    550 
    551 
    552     /* private members and methods */
    553 
    554     private static final String TAG = "WifiScanner";
    555     private static final boolean DBG = true;
    556 
    557     /* commands for Wifi Service */
    558     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
    559 
    560     /** @hide */
    561     public static final int CMD_SCAN                        = BASE + 0;
    562     /** @hide */
    563     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
    564     /** @hide */
    565     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
    566     /** @hide */
    567     public static final int CMD_GET_SCAN_RESULTS            = BASE + 4;
    568     /** @hide */
    569     public static final int CMD_SCAN_RESULT                 = BASE + 5;
    570     /** @hide */
    571     public static final int CMD_SET_HOTLIST                 = BASE + 6;
    572     /** @hide */
    573     public static final int CMD_RESET_HOTLIST               = BASE + 7;
    574     /** @hide */
    575     public static final int CMD_AP_FOUND                    = BASE + 9;
    576     /** @hide */
    577     public static final int CMD_AP_LOST                     = BASE + 10;
    578     /** @hide */
    579     public static final int CMD_START_TRACKING_CHANGE       = BASE + 11;
    580     /** @hide */
    581     public static final int CMD_STOP_TRACKING_CHANGE        = BASE + 12;
    582     /** @hide */
    583     public static final int CMD_CONFIGURE_WIFI_CHANGE       = BASE + 13;
    584     /** @hide */
    585     public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
    586     /** @hide */
    587     public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
    588     /** @hide */
    589     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
    590     /** @hide */
    591     public static final int CMD_OP_FAILED                   = BASE + 18;
    592     /** @hide */
    593     public static final int CMD_PERIOD_CHANGED              = BASE + 19;
    594     /** @hide */
    595     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
    596 
    597     private Context mContext;
    598     private IWifiScanner mService;
    599 
    600     private static final int INVALID_KEY = 0;
    601     private static int sListenerKey = 1;
    602 
    603     private static final SparseArray sListenerMap = new SparseArray();
    604     private static final Object sListenerMapLock = new Object();
    605 
    606     private static AsyncChannel sAsyncChannel;
    607     private static CountDownLatch sConnected;
    608 
    609     private static final Object sThreadRefLock = new Object();
    610     private static int sThreadRefCount;
    611     private static HandlerThread sHandlerThread;
    612 
    613     /**
    614      * Create a new WifiScanner instance.
    615      * Applications will almost always want to use
    616      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
    617      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
    618      * @param context the application context
    619      * @param service the Binder interface
    620      * @hide
    621      */
    622     public WifiScanner(Context context, IWifiScanner service) {
    623         mContext = context;
    624         mService = service;
    625         init();
    626     }
    627 
    628     private void init() {
    629         synchronized (sThreadRefLock) {
    630             if (++sThreadRefCount == 1) {
    631                 Messenger messenger = null;
    632                 try {
    633                     messenger = mService.getMessenger();
    634                 } catch (RemoteException e) {
    635                     /* do nothing */
    636                 } catch (SecurityException e) {
    637                     /* do nothing */
    638                 }
    639 
    640                 if (messenger == null) {
    641                     sAsyncChannel = null;
    642                     return;
    643                 }
    644 
    645                 sHandlerThread = new HandlerThread("WifiScanner");
    646                 sAsyncChannel = new AsyncChannel();
    647                 sConnected = new CountDownLatch(1);
    648 
    649                 sHandlerThread.start();
    650                 Handler handler = new ServiceHandler(sHandlerThread.getLooper());
    651                 sAsyncChannel.connect(mContext, handler, messenger);
    652                 try {
    653                     sConnected.await();
    654                 } catch (InterruptedException e) {
    655                     Log.e(TAG, "interrupted wait at init");
    656                 }
    657             }
    658         }
    659     }
    660 
    661     private void validateChannel() {
    662         if (sAsyncChannel == null) throw new IllegalStateException(
    663                 "No permission to access and change wifi or a bad initialization");
    664     }
    665 
    666     private static int putListener(Object listener) {
    667         if (listener == null) return INVALID_KEY;
    668         int key;
    669         synchronized (sListenerMapLock) {
    670             do {
    671                 key = sListenerKey++;
    672             } while (key == INVALID_KEY);
    673             sListenerMap.put(key, listener);
    674         }
    675         return key;
    676     }
    677 
    678     private static Object getListener(int key) {
    679         if (key == INVALID_KEY) return null;
    680         synchronized (sListenerMapLock) {
    681             Object listener = sListenerMap.get(key);
    682             return listener;
    683         }
    684     }
    685 
    686     private static int getListenerKey(Object listener) {
    687         if (listener == null) return INVALID_KEY;
    688         synchronized (sListenerMapLock) {
    689             int index = sListenerMap.indexOfValue(listener);
    690             if (index == -1) {
    691                 return INVALID_KEY;
    692             } else {
    693                 return sListenerMap.keyAt(index);
    694             }
    695         }
    696     }
    697 
    698     private static Object removeListener(int key) {
    699         if (key == INVALID_KEY) return null;
    700         synchronized (sListenerMapLock) {
    701             Object listener = sListenerMap.get(key);
    702             sListenerMap.remove(key);
    703             return listener;
    704         }
    705     }
    706 
    707     private static int removeListener(Object listener) {
    708         int key = getListenerKey(listener);
    709         if (key == INVALID_KEY) return key;
    710         synchronized (sListenerMapLock) {
    711             sListenerMap.remove(key);
    712             return key;
    713         }
    714     }
    715 
    716     /** @hide */
    717     public static class OperationResult implements Parcelable {
    718         public int reason;
    719         public String description;
    720 
    721         public OperationResult(int reason, String description) {
    722             this.reason = reason;
    723             this.description = description;
    724         }
    725 
    726         /** Implement the Parcelable interface {@hide} */
    727         public int describeContents() {
    728             return 0;
    729         }
    730 
    731         /** Implement the Parcelable interface {@hide} */
    732         public void writeToParcel(Parcel dest, int flags) {
    733             dest.writeInt(reason);
    734             dest.writeString(description);
    735         }
    736 
    737         /** Implement the Parcelable interface {@hide} */
    738         public static final Creator<OperationResult> CREATOR =
    739                 new Creator<OperationResult>() {
    740                     public OperationResult createFromParcel(Parcel in) {
    741                         int reason = in.readInt();
    742                         String description = in.readString();
    743                         return new OperationResult(reason, description);
    744                     }
    745 
    746                     public OperationResult[] newArray(int size) {
    747                         return new OperationResult[size];
    748                     }
    749                 };
    750     }
    751 
    752     private static class ServiceHandler extends Handler {
    753         ServiceHandler(Looper looper) {
    754             super(looper);
    755         }
    756         @Override
    757         public void handleMessage(Message msg) {
    758             switch (msg.what) {
    759                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
    760                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
    761                         sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
    762                     } else {
    763                         Log.e(TAG, "Failed to set up channel connection");
    764                         // This will cause all further async API calls on the WifiManager
    765                         // to fail and throw an exception
    766                         sAsyncChannel = null;
    767                     }
    768                     sConnected.countDown();
    769                     return;
    770                 case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
    771                     return;
    772                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
    773                     Log.e(TAG, "Channel connection lost");
    774                     // This will cause all further async API calls on the WifiManager
    775                     // to fail and throw an exception
    776                     sAsyncChannel = null;
    777                     getLooper().quit();
    778                     return;
    779             }
    780 
    781             Object listener = getListener(msg.arg2);
    782 
    783             if (listener == null) {
    784                 if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
    785                 return;
    786             } else {
    787                 if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
    788             }
    789 
    790             switch (msg.what) {
    791                     /* ActionListeners grouped together */
    792                 case CMD_OP_SUCCEEDED :
    793                     ((ActionListener) listener).onSuccess();
    794                     break;
    795                 case CMD_OP_FAILED : {
    796                         OperationResult result = (OperationResult)msg.obj;
    797                         ((ActionListener) listener).onFailure(result.reason, result.description);
    798                         removeListener(msg.arg2);
    799                     }
    800                     break;
    801                 case CMD_SCAN_RESULT :
    802                     ((ScanListener) listener).onResults(
    803                             ((ParcelableScanResults) msg.obj).getResults());
    804                     return;
    805                 case CMD_FULL_SCAN_RESULT :
    806                     ScanResult result = (ScanResult) msg.obj;
    807                     ((ScanListener) listener).onFullResult(result);
    808                     return;
    809                 case CMD_PERIOD_CHANGED:
    810                     ((ScanListener) listener).onPeriodChanged(msg.arg1);
    811                     return;
    812                 case CMD_AP_FOUND:
    813                     ((BssidListener) listener).onFound(
    814                             ((ParcelableScanResults) msg.obj).getResults());
    815                     return;
    816                 case CMD_WIFI_CHANGE_DETECTED:
    817                     ((WifiChangeListener) listener).onChanging(
    818                             ((ParcelableScanResults) msg.obj).getResults());
    819                    return;
    820                 case CMD_WIFI_CHANGES_STABILIZED:
    821                     ((WifiChangeListener) listener).onQuiescence(
    822                             ((ParcelableScanResults) msg.obj).getResults());
    823                     return;
    824                 default:
    825                     if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
    826                     return;
    827             }
    828         }
    829     }
    830 }
    831