Add support for batched wifi scans.

bug:9301872

Change-Id: I5a7edfdbd2b78a65119d11acad491eae350c0870
This commit is contained in:
Robert Greenwalt
2013-08-01 18:24:13 -07:00
parent 7a605df313
commit 0451d59ba2
13 changed files with 994 additions and 30 deletions

View File

@@ -384,6 +384,8 @@ aidl_files := \
frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \
frameworks/base/wifi/java/android/net/wifi/BatchedScanSettings.aidl \
frameworks/base/wifi/java/android/net/wifi/BatchedScanResult.aidl \
gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
$(gen): PRIVATE_SRC_FILES := $(aidl_files)

View File

@@ -353,6 +353,9 @@
Default value is 2 minutes. -->
<integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
<!-- Wifi driver supports batched scan -->
<bool translatable="false" name="config_wifi_batched_scan_supported">false</bool>
<!-- Flag indicating whether the we should enable the automatic brightness in Settings.
Software implementation will be used if config_hardware_auto_brightness_available is not set -->
<bool name="config_automatic_brightness_available">false</bool>

View File

@@ -281,7 +281,8 @@
<java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
<java-symbol type="bool" name="config_useFixedVolume" />
<java-symbol type="bool" name="config_forceDefaultOrientation" />
<java-symbol type="bool" name="config_wifi_batched_scan_supported" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />

View File

@@ -25,18 +25,20 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiWatchdogStateMachine;
import android.net.DhcpInfo;
import android.net.DhcpResults;
import android.net.LinkAddress;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
import android.net.wifi.BatchedScanResult;
import android.net.wifi.BatchedScanSettings;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WifiWatchdogStateMachine;
import android.os.Binder;
import android.os.Handler;
import android.os.Messenger;
@@ -63,6 +65,7 @@ import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -121,6 +124,8 @@ public final class WifiService extends IWifiManager.Stub {
/* Tracks the persisted states for wi-fi & airplane mode */
final WifiSettingsStore mSettingsStore;
final boolean mBatchedScanSupported;
/**
* Asynchronous channel to WifiStateMachine
*/
@@ -246,6 +251,9 @@ public final class WifiService extends IWifiManager.Stub {
mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
mWifiController.start();
mBatchedScanSupported = mContext.getResources().getBoolean(
R.bool.config_wifi_batched_scan_supported);
registerForScanModeChange();
mContext.registerReceiver(
new BroadcastReceiver() {
@@ -314,6 +322,142 @@ public final class WifiService extends IWifiManager.Stub {
mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
}
private class BatchedScanRequest extends DeathRecipient {
BatchedScanSettings settings;
int uid;
BatchedScanRequest(BatchedScanSettings settings, IBinder binder, int uid) {
super(0, null, binder, null);
this.settings = settings;
this.uid = uid;
}
public void binderDied() {
stopBatchedScan(settings, mBinder);
}
public String toString() {
return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
}
}
private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
public boolean isBatchedScanSupported() {
return mBatchedScanSupported;
}
/**
* see {@link android.net.wifi.WifiManager#requestBatchedScan()}
*/
public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
enforceChangePermission();
if (mBatchedScanSupported == false) return false;
requested = new BatchedScanSettings(requested);
if (requested.isInvalid()) return false;
BatchedScanRequest r = new BatchedScanRequest(requested, binder, Binder.getCallingUid());
synchronized(mBatchedScanners) {
mBatchedScanners.add(r);
resolveBatchedScannersLocked();
}
return true;
}
public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
enforceAccessPermission();
if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return new ArrayList<BatchedScanResult>();
}
int currentUser = ActivityManager.getCurrentUser();
if (userId != currentUser) {
return new ArrayList<BatchedScanResult>();
} else {
return mWifiStateMachine.syncGetBatchedScanResultsList();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
public void stopBatchedScan(BatchedScanSettings settings, IBinder binder) {
enforceChangePermission();
if (mBatchedScanSupported == false) return;
synchronized(mBatchedScanners) {
BatchedScanRequest found = null;
for (BatchedScanRequest r : mBatchedScanners) {
if (r.mBinder.equals(binder) && r.settings.equals(settings)) {
found = r;
break;
}
}
if (found != null) {
mBatchedScanners.remove(found);
resolveBatchedScannersLocked();
}
}
}
private void resolveBatchedScannersLocked() {
BatchedScanSettings setting = new BatchedScanSettings();
setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
int responsibleUid = 0;
setting.channelSet = new ArrayList<String>();
if (mBatchedScanners.size() == 0) {
mWifiStateMachine.setBatchedScanSettings(null, 0);
return;
}
for (BatchedScanRequest r : mBatchedScanners) {
BatchedScanSettings s = r.settings;
if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
s.maxScansPerBatch < setting.maxScansPerBatch) {
setting.maxScansPerBatch = s.maxScansPerBatch;
responsibleUid = r.uid;
}
if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
s.maxApPerScan > setting.maxApPerScan) {
setting.maxApPerScan = s.maxApPerScan;
}
if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
s.scanIntervalSec < setting.scanIntervalSec) {
setting.scanIntervalSec = s.scanIntervalSec;
responsibleUid = r.uid;
}
if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
s.maxApForDistance > setting.maxApForDistance) {
setting.maxApForDistance = s.maxApForDistance;
}
if (s.channelSet != null) {
for (String i : s.channelSet) {
if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
}
}
}
if (setting.channelSet.size() == 0) setting.channelSet = null;
if (setting.scanIntervalSec < BatchedScanSettings.MIN_INTERVAL_SEC) {
setting.scanIntervalSec = BatchedScanSettings.MIN_INTERVAL_SEC;
}
if (setting.maxScansPerBatch == BatchedScanSettings.UNSPECIFIED) {
setting.maxScansPerBatch = BatchedScanSettings.DEFAULT_SCANS_PER_BATCH;
}
if (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED) {
setting.maxApPerScan = BatchedScanSettings.DEFAULT_AP_PER_SCAN;
}
if (setting.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
}
if (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED) {
setting.maxApForDistance = BatchedScanSettings.DEFAULT_AP_FOR_DISTANCE;
}
mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
"WifiService");
@@ -569,11 +713,11 @@ public final class WifiService extends IWifiManager.Stub {
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return new ArrayList<ScanResult>();
}
try {
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return new ArrayList<ScanResult>();
}
int currentUser = ActivityManager.getCurrentUser();
if (userId != currentUser) {
return new ArrayList<ScanResult>();

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2013, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi;
parcelable BatchedScanResult;

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi;
import android.os.Parcelable;
import android.os.Parcel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Describes the Results of a batched set of wifi scans where the firmware performs many
* scans and stores the timestamped results without waking the main processor each time.
* @hide pending review
*/
public class BatchedScanResult implements Parcelable {
private static final String TAG = "BatchedScanResult";
/** Inidcates this scan was interrupted and may only have partial results. */
public boolean truncated;
/** The result of this particular scan. */
public final List<ScanResult> scanResults = new ArrayList<ScanResult>();
public BatchedScanResult() {
}
public BatchedScanResult(BatchedScanResult source) {
truncated = source.truncated;
for (ScanResult s : source.scanResults) scanResults.add(new ScanResult(s));
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("BatchedScanResult: ").
append("truncated: ").append(String.valueOf(truncated)).
append("scanResults: [");
for (ScanResult s : scanResults) {
sb.append(" <").append(s.toString()).append("> ");
}
sb.append(" ]");
return sb.toString();
}
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(truncated ? 1 : 0);
dest.writeInt(scanResults.size());
for (ScanResult s : scanResults) {
s.writeToParcel(dest, flags);
}
}
/** Implement the Parcelable interface {@hide} */
public static final Creator<BatchedScanResult> CREATOR =
new Creator<BatchedScanResult>() {
public BatchedScanResult createFromParcel(Parcel in) {
BatchedScanResult result = new BatchedScanResult();
result.truncated = (in.readInt() == 1);
int count = in.readInt();
while (count-- > 0) {
result.scanResults.add(ScanResult.CREATOR.createFromParcel(in));
}
return result;
}
public BatchedScanResult[] newArray(int size) {
return new BatchedScanResult[size];
}
};
}

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2013, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi;
parcelable BatchedScanSettings;

View File

@@ -0,0 +1,226 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi;
import android.os.Parcelable;
import android.os.Parcel;
import java.util.ArrayList;
import java.util.Collection;
/**
* Describes the settings for batched wifi scans where the firmware performs many
* scans and stores the timestamped results without waking the main processor each time.
* This can give information over time with minimal battery impact.
* @hide pending review
*/
public class BatchedScanSettings implements Parcelable {
private static final String TAG = "BatchedScanSettings";
/** Used to indicate no preference for an int value */
public final static int UNSPECIFIED = Integer.MAX_VALUE;
// TODO - make MIN/mAX dynamic and gservices adjustable.
public final static int MIN_SCANS_PER_BATCH = 2;
public final static int MAX_SCANS_PER_BATCH = 255;
public final static int DEFAULT_SCANS_PER_BATCH = MAX_SCANS_PER_BATCH;
public final static int MIN_AP_PER_SCAN = 2;
public final static int MAX_AP_PER_SCAN = 255;
public final static int DEFAULT_AP_PER_SCAN = 16;
public final static int MIN_INTERVAL_SEC = 0;
public final static int MAX_INTERVAL_SEC = 3600;
public final static int DEFAULT_INTERVAL_SEC = 30;
public final static int MIN_AP_FOR_DISTANCE = 0;
public final static int MAX_AP_FOR_DISTANCE = MAX_AP_PER_SCAN;
public final static int DEFAULT_AP_FOR_DISTANCE = 0;
/** The expected number of scans per batch. Note that the firmware may drop scans
* leading to fewer scans during the normal batch scan duration. This value need not
* be specified (may be set to {@link UNSPECIFIED}) by the application and we will try
* to scan as many times as the firmware can support. If another app requests fewer
* scans per batch we will attempt to honor that.
*/
public int maxScansPerBatch;
/** The maximum desired AP listed per scan. Fewer AP may be returned if that's all
* that the driver detected. If another application requests more AP per scan that
* will take precedence. The if more channels are detected than we request, the APs
* with the lowest signal strength will be dropped.
*/
public int maxApPerScan;
/** The channels used in the scan. If all channels should be used, {@code null} may be
* specified. If another application requests more channels or all channels, that
* will take precedence.
*/
public Collection<String> channelSet;
/** The time between the start of two sequential scans, in seconds. If another
* application requests more frequent scans, that will take precedence. If this
* value is less than the duration of a scan, the next scan should start immediately.
*/
public int scanIntervalSec;
/** The number of the best (strongest signal) APs for which the firmware will
* attempt to get distance information (RTT). Not all firmware supports this
* feature, so it may be ignored. If another application requests a greater
* number, that will take precedence.
*/
public int maxApForDistance;
public BatchedScanSettings() {
clear();
}
public void clear() {
maxScansPerBatch = UNSPECIFIED;
maxApPerScan = UNSPECIFIED;
channelSet = null;
scanIntervalSec = UNSPECIFIED;
maxApForDistance = UNSPECIFIED;
}
public BatchedScanSettings(BatchedScanSettings source) {
maxScansPerBatch = source.maxScansPerBatch;
maxApPerScan = source.maxApPerScan;
if (source.channelSet != null) {
channelSet = new ArrayList(source.channelSet);
}
scanIntervalSec = source.scanIntervalSec;
maxApForDistance = source.maxApForDistance;
}
private boolean channelSetIsValid() {
if (channelSet == null || channelSet.isEmpty()) return true;
for (String channel : channelSet) {
try {
int i = Integer.parseInt(channel);
if (i > 0 && i < 197) continue;
} catch (NumberFormatException e) {}
if (channel.equals("A") || channel.equals("B")) continue;
return false;
}
return true;
}
/** @hide */
public boolean isInvalid() {
if (maxScansPerBatch != UNSPECIFIED && (maxScansPerBatch < MIN_SCANS_PER_BATCH ||
maxScansPerBatch > MAX_SCANS_PER_BATCH)) return true;
if (maxApPerScan != UNSPECIFIED && (maxApPerScan < MIN_AP_PER_SCAN ||
maxApPerScan > MAX_AP_PER_SCAN)) return true;
if (channelSetIsValid() == false) return true;
if (scanIntervalSec != UNSPECIFIED && (scanIntervalSec < MIN_INTERVAL_SEC ||
scanIntervalSec > MAX_INTERVAL_SEC)) return true;
if (maxApForDistance != UNSPECIFIED && (maxApForDistance < MIN_AP_FOR_DISTANCE ||
maxApForDistance > MAX_AP_FOR_DISTANCE)) return true;
return false;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BatchedScanSettings == false) return false;
BatchedScanSettings o = (BatchedScanSettings)obj;
if (maxScansPerBatch != o.maxScansPerBatch ||
maxApPerScan != o.maxApPerScan ||
scanIntervalSec != o.scanIntervalSec ||
maxApForDistance != o.maxApForDistance) return false;
if (channelSet == null) {
return (o.channelSet == null);
}
return channelSet.equals(o.channelSet);
}
@Override
public int hashCode() {
return maxScansPerBatch +
(maxApPerScan * 3) +
(scanIntervalSec * 5) +
(maxApForDistance * 7) +
(channelSet.hashCode() * 11);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
String none = "<none>";
sb.append("BatchScanSettings [maxScansPerBatch: ").
append(maxScansPerBatch == UNSPECIFIED ? none : maxScansPerBatch).
append(", maxApPerScan: ").append(maxApPerScan == UNSPECIFIED? none : maxApPerScan).
append(", scanIntervalSec: ").
append(scanIntervalSec == UNSPECIFIED ? none : scanIntervalSec).
append(", maxApForDistance: ").
append(maxApForDistance == UNSPECIFIED ? none : maxApForDistance).
append(", channelSet: ");
if (channelSet == null) {
sb.append("ALL");
} else {
sb.append("<");
for (String channel : channelSet) {
sb.append(" " + channel);
}
sb.append(">");
}
sb.append("]");
return sb.toString();
}
/** Implement the Parcelable interface {@hide} */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(maxScansPerBatch);
dest.writeInt(maxApPerScan);
dest.writeInt(scanIntervalSec);
dest.writeInt(maxApForDistance);
dest.writeInt(channelSet == null ? 0 : channelSet.size());
if (channelSet != null) {
for (String channel : channelSet) dest.writeString(channel);
}
}
/** Implement the Parcelable interface {@hide} */
public static final Creator<BatchedScanSettings> CREATOR =
new Creator<BatchedScanSettings>() {
public BatchedScanSettings createFromParcel(Parcel in) {
BatchedScanSettings settings = new BatchedScanSettings();
settings.maxScansPerBatch = in.readInt();
settings.maxApPerScan = in.readInt();
settings.scanIntervalSec = in.readInt();
settings.maxApForDistance = in.readInt();
int channelCount = in.readInt();
if (channelCount > 0) {
settings.channelSet = new ArrayList(channelCount);
while (channelCount-- > 0) {
settings.channelSet.add(in.readString());
}
}
return settings;
}
public BatchedScanSettings[] newArray(int size) {
return new BatchedScanSettings[size];
}
};
}

View File

@@ -16,8 +16,10 @@
package android.net.wifi;
import android.net.wifi.WifiInfo;
import android.net.wifi.BatchedScanResult;
import android.net.wifi.BatchedScanSettings;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.ScanResult;
import android.net.DhcpInfo;
@@ -114,5 +116,13 @@ interface IWifiManager
void enableTdls(String remoteIPAddress, boolean enable);
void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder);
void stopBatchedScan(in BatchedScanSettings requested, IBinder binder);
List<BatchedScanResult> getBatchedScanResults(String callingPackage);
boolean isBatchedScanSupported();
}

View File

@@ -54,7 +54,26 @@ public class ScanResult implements Parcelable {
* Time Synchronization Function (tsf) timestamp in microseconds when
* this result was last seen.
*/
public long timestamp;
public long timestamp;
/**
* The approximate distance to the AP in centimeter, if available. Else
* {@link UNSPECIFIED}.
* {@hide}
*/
public int distanceCm;
/**
* The standard deviation of the distance to the AP, if available.
* Else {@link UNSPECIFIED}.
* {@hide}
*/
public int distanceSdCm;
/**
* {@hide}
*/
public final static int UNSPECIFIED = -1;
/** {@hide} */
public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
@@ -66,8 +85,23 @@ public class ScanResult implements Parcelable {
this.level = level;
this.frequency = frequency;
this.timestamp = tsf;
this.distanceCm = UNSPECIFIED;
this.distanceSdCm = UNSPECIFIED;
}
/** {@hide} */
public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
long tsf, int distCm, int distSdCm) {
this.wifiSsid = wifiSsid;
this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
this.BSSID = BSSID;
this.capabilities = caps;
this.level = level;
this.frequency = frequency;
this.timestamp = tsf;
this.distanceCm = distCm;
this.distanceSdCm = distSdCm;
}
/** copy constructor {@hide} */
public ScanResult(ScanResult source) {
@@ -79,6 +113,8 @@ public class ScanResult implements Parcelable {
level = source.level;
frequency = source.frequency;
timestamp = source.timestamp;
distanceCm = source.distanceCm;
distanceSdCm = source.distanceSdCm;
}
}
@@ -100,6 +136,11 @@ public class ScanResult implements Parcelable {
append(", timestamp: ").
append(timestamp);
sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
append("(cm)");
sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
append("(cm)");
return sb.toString();
}
@@ -121,6 +162,8 @@ public class ScanResult implements Parcelable {
dest.writeInt(level);
dest.writeInt(frequency);
dest.writeLong(timestamp);
dest.writeInt(distanceCm);
dest.writeInt(distanceSdCm);
}
/** Implement the Parcelable interface {@hide} */
@@ -137,7 +180,9 @@ public class ScanResult implements Parcelable {
in.readString(),
in.readInt(),
in.readInt(),
in.readLong()
in.readLong(),
in.readInt(),
in.readInt()
);
}

View File

@@ -35,6 +35,7 @@ import android.util.SparseArray;
import java.net.InetAddress;
import java.util.concurrent.CountDownLatch;
import com.android.internal.R;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
@@ -364,6 +365,14 @@ public class WifiManager {
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
/**
* A batch of access point scans has been completed and the results areavailable.
* Call {@link #getBatchedScanResults()} to obtain the results.
* @hide pending review
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String BATCHED_SCAN_RESULTS_AVAILABLE_ACTION =
"android.net.wifi.BATCHED_RESULTS";
/**
* The RSSI (signal strength) has changed.
* @see #EXTRA_NEW_RSSI
@@ -777,6 +786,59 @@ public class WifiManager {
}
}
/**
* Request a batched scan for access points. To end your requested batched scan,
* call stopBatchedScan with the same Settings.
*
* If there are mulitple requests for batched scans, the more demanding settings will
* take precidence.
*
* @param requested {@link BatchedScanSettings} the scan settings requested.
* @return false on known error
* @hide
*/
public boolean requestBatchedScan(BatchedScanSettings requested) {
try {
return mService.requestBatchedScan(requested, new Binder());
} catch (RemoteException e) { return false; }
}
/**
* Check if the Batched Scan feature is supported.
*
* @return false if not supported.
* @hide
*/
public boolean isBatchedScanSupported() {
try {
return mService.isBatchedScanSupported();
} catch (RemoteException e) { return false; }
}
/**
* End a requested batch scan for this applicaiton. Note that batched scan may
* still occur if other apps are using them.
* @hide
*/
public void stopBatchedScan(BatchedScanSettings requested) {
try {
mService.stopBatchedScan(requested, new Binder());
} catch (RemoteException e) {}
}
/**
* Retrieve the latest batched scan result. This should be called immediately after
* {@link BATCHED_SCAN_RESULTS_AVAILABLE_ACTION} is received.
* @hide
*/
public List<BatchedScanResult> getBatchedScanResults() {
try {
return mService.getBatchedScanResults(mContext.getBasePackageName());
} catch (RemoteException e) {
return null;
}
}
/**
* Return dynamic information about the current Wi-Fi connection, if any is active.
* @return the Wi-Fi information, contained in {@link WifiInfo}.

View File

@@ -216,6 +216,40 @@ public class WifiNative {
return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
}
/**
* Format of command
* DRIVER WLS_BATCHING SET SCAN_FRQ=x BESTN=y CHANNEL=<z, w, t> RTT=s
* where x is an ascii representation of an integer number of seconds between scans
* y is an ascii representation of an integer number of the max AP to remember per scan
* z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
* indicating entire ranges of channels
* s is an ascii representation of an integer number of highest-strength AP
* for which we'd like approximate distance reported
*
* The return value is an ascii integer representing a guess of the number of scans
* the firmware can remember before it runs out of buffer space or -1 on error
*/
public String setBatchedScanSettings(BatchedScanSettings settings) {
if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP");
String cmd = "DRIVER WLS_BATCHING SET SCAN_FRQ=" + settings.scanIntervalSec;
if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
cmd += " BESTN " + settings.maxApPerScan;
}
if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
cmd += " CHANNEL=<";
for (String channel : settings.channelSet) cmd += " " + channel;
cmd += ">";
}
if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
cmd += " RTT=" + settings.maxApForDistance;
}
return doStringCommand(cmd);
}
public String getBatchedScanResults() {
return doStringCommand("DRIVER WLS_BATCHING GET");
}
public boolean startDriver() {
return doBooleanCommand("DRIVER START");
}

View File

@@ -122,6 +122,11 @@ public class WifiStateMachine extends StateMachine {
private static final int SCAN_RESULT_CACHE_SIZE = 80;
private final LruCache<String, ScanResult> mScanResultCache;
/* Batch scan results */
private final List<BatchedScanResult> mBatchedScanResults =
new ArrayList<BatchedScanResult>();
private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
/* Chipset supports background scan */
private final boolean mBackgroundScanSupported;
@@ -210,6 +215,7 @@ public class WifiStateMachine extends StateMachine {
private AlarmManager mAlarmManager;
private PendingIntent mScanIntent;
private PendingIntent mDriverStopIntent;
private PendingIntent mBatchedScanIntervalIntent;
/* Tracks current frequency mode */
private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
@@ -355,6 +361,13 @@ public class WifiStateMachine extends StateMachine {
public static final int CMD_BOOT_COMPLETED = BASE + 134;
/* change the batch scan settings.
* arg1 = responsible UID
* obj = the new settings
*/
public static final int CMD_SET_BATCH_SCAN = BASE + 135;
public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136;
public static final int CONNECT_MODE = 1;
public static final int SCAN_ONLY_MODE = 2;
public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
@@ -519,6 +532,8 @@ public class WifiStateMachine extends StateMachine {
private static final String ACTION_DELAYED_DRIVER_STOP =
"com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
private static final String ACTION_REFRESH_BATCHED_SCAN =
"com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
/**
* Keep track of whether WIFI is running.
*/
@@ -541,6 +556,9 @@ public class WifiStateMachine extends StateMachine {
private final IBatteryStats mBatteryStats;
private BatchedScanSettings mBatchedScanSettings = null;
public WifiStateMachine(Context context, String wlanInterface) {
super("WifiStateMachine");
@@ -577,6 +595,9 @@ public class WifiStateMachine extends StateMachine {
Intent scanIntent = new Intent(ACTION_START_SCAN, null);
mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
Intent batchedIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
mBatchedScanIntervalIntent = PendingIntent.getBroadcast(mContext, 0, batchedIntent, 0);
mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
R.integer.config_wifi_framework_scan_interval);
@@ -614,22 +635,25 @@ public class WifiStateMachine extends StateMachine {
},
new IntentFilter(ACTION_START_SCAN));
IntentFilter screenFilter = new IntentFilter();
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver screenReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
handleScreenStateChanged(true);
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
handleScreenStateChanged(false);
}
}
};
mContext.registerReceiver(screenReceiver, screenFilter);
if (action.equals(Intent.ACTION_SCREEN_ON)) {
handleScreenStateChanged(true);
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
handleScreenStateChanged(false);
} else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
startNextBatchedScanAsync();
}
}
}, filter);
mContext.registerReceiver(
new BroadcastReceiver() {
@@ -738,6 +762,269 @@ public class WifiStateMachine extends StateMachine {
sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
}
/**
* start or stop batched scanning using the given settings
*/
public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) {
sendMessage(CMD_SET_BATCH_SCAN, callingUid, 0, settings);
}
public List<BatchedScanResult> syncGetBatchedScanResultsList() {
synchronized (mBatchedScanResults) {
List<BatchedScanResult> batchedScanList =
new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
for(BatchedScanResult result: mBatchedScanResults) {
batchedScanList.add(new BatchedScanResult(result));
}
return batchedScanList;
}
}
private void startBatchedScan() {
// first grab any existing data
retrieveBatchedScanData();
mAlarmManager.cancel(mBatchedScanIntervalIntent);
String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
try {
int expected = Integer.parseInt(scansExpected);
setNextBatchedAlarm(expected);
} catch (NumberFormatException e) {
loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
}
}
// called from BroadcastListener
private void startNextBatchedScanAsync() {
sendMessage(CMD_START_NEXT_BATCHED_SCAN);
}
private void startNextBatchedScan() {
// first grab any existing data
int nextCount = retrieveBatchedScanData();
setNextBatchedAlarm(nextCount);
}
// return true if new/different
private boolean recordBatchedScanSettings(BatchedScanSettings settings) {
if (DBG) log("set batched scan to " + settings);
if (settings != null) {
// TODO - noteBatchedScanStart(message.arg1);
if (settings.equals(mBatchedScanSettings)) return false;
} else {
if (mBatchedScanSettings == null) return false;
// TODO - noteBatchedScanStop(message.arg1);
}
mBatchedScanSettings = settings;
return true;
}
private void stopBatchedScan() {
mAlarmManager.cancel(mBatchedScanIntervalIntent);
retrieveBatchedScanData();
mWifiNative.setBatchedScanSettings(null);
}
private void setNextBatchedAlarm(int scansExpected) {
if (mBatchedScanSettings == null || scansExpected < 1) return;
if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
scansExpected = mBatchedScanSettings.maxScansPerBatch;
}
int secToFull = mBatchedScanSettings.scanIntervalSec;
secToFull *= scansExpected;
int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
if (debugPeriod > 0) secToFull = debugPeriod;
// set the alarm to do the next poll. We set it a little short as we'd rather
// wake up wearly than miss a scan due to buffer overflow
mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
mBatchedScanIntervalIntent);
}
/**
* Start reading new scan data
* Data comes in as:
* "scancount=5\n"
* "nextcount=5\n"
* "apcount=3\n"
* "trunc\n" (optional)
* "bssid=...\n"
* "ssid=...\n"
* "freq=...\n" (in Mhz)
* "level=...\n"
* "dist=...\n" (in cm)
* "distsd=...\n" (standard deviation, in cm)
* "===="
* "bssid=...\n"
* etc
* "===="
* "bssid=...\n"
* etc
* "%%%%"
* "apcount=2\n"
* "bssid=...\n"
* etc
* "%%%%
* etc
* "----"
*/
private int retrieveBatchedScanData() {
String rawData = mWifiNative.getBatchedScanResults();
if (rawData == null) {
loge("Unexpected null BatchedScanResults");
return 0;
}
int nextCount = 0;
int scanCount = 0;
final String END_OF_SCAN = "====";
final String END_OF_BATCH = "%%%%";
final String END_OF_BATCHES = "----";
final String SCANCOUNT = "scancount=";
final String NEXTCOUNT = "nextcount=";
final String TRUNCATED = "trunc";
final String APCOUNT = "apcount=";
final String AGE = "age=";
final String DIST = "dist=";
final String DISTSD = "distsd=";
String splitData[] = rawData.split("\n");
int n = 0;
if (splitData[n].startsWith(SCANCOUNT)) {
try {
scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
} catch (NumberFormatException e) {}
}
if (scanCount == 0) {
loge("scanCount not found");
return 0;
}
if (splitData[n].startsWith(NEXTCOUNT)) {
try {
nextCount = Integer.parseInt(splitData[n++].substring(NEXTCOUNT.length()));
} catch (NumberFormatException e) {}
}
if (nextCount == 0) {
loge("nextCount not found");
return 0;
}
final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
synchronized (mBatchedScanResults) {
mBatchedScanResults.clear();
BatchedScanResult batchedScanResult = new BatchedScanResult();
String bssid = null;
WifiSsid wifiSsid = null;
int level = 0;
int freq = 0;
int dist, distSd;
long tsf = 0;
dist = distSd = ScanResult.UNSPECIFIED;
long now = System.currentTimeMillis();
while (true) {
while (n < splitData.length) {
if (splitData[n].equals(END_OF_BATCHES)) {
if (++n != splitData.length) {
loge("didn't consume " + (splitData.length-n));
}
if (mBatchedScanResults.size() > 0) {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
return nextCount;
}
if ((splitData[n].equals(END_OF_SCAN)) || splitData[n].equals(END_OF_BATCH)) {
if (bssid != null) {
batchedScanResult.scanResults.add(new ScanResult(
wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
wifiSsid = null;
bssid = null;
level = 0;
freq = 0;
tsf = 0;
dist = distSd = ScanResult.UNSPECIFIED;
}
if (splitData[n].equals(END_OF_BATCH)) {
if (batchedScanResult.scanResults.size() != 0) {
mBatchedScanResults.add(batchedScanResult);
batchedScanResult = new BatchedScanResult();
} else {
logd("Found empty batch");
}
}
n++;
} else if (splitData[n].equals(BSSID_STR)) {
bssid = splitData[n++].substring(BSSID_STR.length());
} else if (splitData[n].equals(FREQ_STR)) {
try {
freq = Integer.parseInt(splitData[n++].substring(FREQ_STR.length()));
} catch (NumberFormatException e) {
loge("Invalid freqency: " + splitData[n-1]);
freq = 0;
}
} else if (splitData[n].equals(AGE)) {
try {
tsf = now - Long.parseLong(splitData[n++].substring(AGE.length()));
} catch (NumberFormatException e) {
loge("Invalid timestamp: " + splitData[n-1]);
tsf = 0;
}
} else if (splitData[n].equals(SSID_STR)) {
wifiSsid = WifiSsid.createFromAsciiEncoded(
splitData[n++].substring(SSID_STR.length()));
} else if (splitData[n].equals(LEVEL_STR)) {
try {
level = Integer.parseInt(splitData[n++].substring(LEVEL_STR.length()));
if (level > 0) level -= 256;
} catch (NumberFormatException e) {
loge("Invalid level: " + splitData[n-1]);
level = 0;
}
} else if (splitData[n].equals(DIST)) {
try {
dist = Integer.parseInt(splitData[n++].substring(DIST.length()));
} catch (NumberFormatException e) {
loge("Invalid distance: " + splitData[n-1]);
dist = ScanResult.UNSPECIFIED;
}
} else if (splitData[n].equals(DISTSD)) {
try {
distSd = Integer.parseInt(splitData[n++].substring(DISTSD.length()));
} catch (NumberFormatException e) {
loge("Invalid distanceSd: " + splitData[n-1]);
distSd = ScanResult.UNSPECIFIED;
}
}
}
rawData = mWifiNative.getBatchedScanResults();
if (rawData == null) {
loge("Unexpected null BatchedScanResults");
return nextCount;
}
splitData = rawData.split("\n");
if (splitData.length == 0 || splitData[0].equals("ok")) {
loge("batch scan results just ended!");
if (mBatchedScanResults.size() > 0) {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
return nextCount;
}
n = 0;
}
}
}
// If workSource is not null, blame is given to it, otherwise blame is given to callingUid.
private void noteScanStart(int callingUid, WorkSource workSource) {
if (mScanWorkSource == null && (callingUid != UNKNOWN_SCAN_SOURCE || workSource != null)) {
@@ -1979,6 +2266,12 @@ public class WifiStateMachine extends StateMachine {
sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
}
break;
case CMD_SET_BATCH_SCAN:
recordBatchedScanSettings((BatchedScanSettings)message.obj);
break;
case CMD_START_NEXT_BATCHED_SCAN:
startNextBatchedScan();
break;
/* Discard */
case CMD_START_SCAN:
case CMD_START_SUPPLICANT:
@@ -2472,6 +2765,10 @@ public class WifiStateMachine extends StateMachine {
mWifiNative.stopFilteringMulticastV4Packets();
}
if (mBatchedScanSettings != null) {
startBatchedScan();
}
if (mOperationalMode != CONNECT_MODE) {
mWifiNative.disconnect();
transitionTo(mScanModeState);
@@ -2513,6 +2810,10 @@ public class WifiStateMachine extends StateMachine {
noteScanStart(message.arg1, (WorkSource) message.obj);
startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
break;
case CMD_SET_BATCH_SCAN:
recordBatchedScanSettings((BatchedScanSettings)message.obj);
startBatchedScan();
break;
case CMD_SET_COUNTRY_CODE:
String country = (String) message.obj;
if (DBG) log("set country code " + country);
@@ -2641,6 +2942,10 @@ public class WifiStateMachine extends StateMachine {
updateBatteryWorkSource(null);
mScanResults = new ArrayList<ScanResult>();
if (mBatchedScanSettings != null) {
stopBatchedScan();
}
final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);