diff options
| -rw-r--r-- | Android.mk | 4 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 16 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 13 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 1 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/IRttCallback.aidl | 32 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl | 29 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/RangingRequest.aidl | 19 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/RangingRequest.java | 237 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/RangingResult.aidl | 19 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/RangingResult.java | 194 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/RangingResultCallback.java | 56 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/rtt/WifiRttManager.java | 100 | ||||
| -rw-r--r-- | wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java | 226 |
13 files changed, 944 insertions, 2 deletions
diff --git a/Android.mk b/Android.mk index 0e24399f7793..1a99b1d87b4f 100644 --- a/Android.mk +++ b/Android.mk @@ -551,6 +551,8 @@ LOCAL_SRC_FILES += \ wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \ wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ + wifi/java/android/net/wifi/rtt/IRttCallback.aidl \ + wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \ wifi/java/android/net/wifi/IWifiScanner.aidl \ wifi/java/android/net/wifi/IRttManager.aidl \ packages/services/PacProcessor/com/android/net/IProxyService.aidl \ @@ -719,6 +721,8 @@ aidl_files := \ frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl \ frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl \ frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \ + frameworks/base/wifi/java/android/net/wifi/rtt/RangingRequest.aidl \ + frameworks/base/wifi/java/android/net/wifi/rtt/RangingResult.aidl \ frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \ frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \ frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \ diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ab70f0e71216..50f1f364b9e5 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -81,10 +81,10 @@ import android.net.INetworkPolicyManager; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; -import android.net.nsd.INsdManager; -import android.net.nsd.NsdManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; import android.net.wifi.IRttManager; import android.net.wifi.IWifiManager; import android.net.wifi.IWifiScanner; @@ -95,6 +95,8 @@ import android.net.wifi.aware.IWifiAwareManager; import android.net.wifi.aware.WifiAwareManager; import android.net.wifi.p2p.IWifiP2pManager; import android.net.wifi.p2p.WifiP2pManager; +import android.net.wifi.rtt.IWifiRttManager; +import android.net.wifi.rtt.WifiRttManager; import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; @@ -603,6 +605,16 @@ final class SystemServiceRegistry { ConnectivityThread.getInstanceLooper()); }}); + registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class, + new CachedServiceFetcher<WifiRttManager>() { + @Override + public WifiRttManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE); + IWifiRttManager service = IWifiRttManager.Stub.asInterface(b); + return new WifiRttManager(ctx.getOuterContext(), service); + }}); + registerService(Context.ETHERNET_SERVICE, EthernetManager.class, new CachedServiceFetcher<EthernetManager>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2d8249acb5bf..5315a4f46daa 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3464,6 +3464,19 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi + * + * Note: this is a replacement for WIFI_RTT_SERVICE above. It will + * be renamed once final implementation in place. + * + * @see #getSystemService + * @see android.net.wifi.rtt.WifiRttManager + * @hide + */ + public static final String WIFI_RTT2_SERVICE = "rttmanager2"; + + /** + * Use with {@link #getSystemService} to retrieve a {@link * android.net.lowpan.LowpanManager} for handling management of * LoWPAN access. * diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 57271fa10950..6fc614ba9f09 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1079,6 +1079,7 @@ public final class SystemServer { if (!disableRtt) { traceBeginAndSlog("StartWifiRtt"); mSystemServiceManager.startService("com.android.server.wifi.RttService"); + mSystemServiceManager.startService("com.android.server.wifi.rtt.RttService"); traceEnd(); } diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl new file mode 100644 index 000000000000..fb1636f87297 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 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.rtt; + +import android.net.wifi.rtt.RangingResult; + +/** + * Interface for RTT result callback. + * + * @hide + */ +oneway interface IRttCallback +{ + /** + * Service to manager callback providing RTT status and results. + */ + void onRangingResults(int status, in List<RangingResult> results); +} diff --git a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl new file mode 100644 index 000000000000..ad92e040b4be --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 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.rtt; + +import android.net.wifi.rtt.IRttCallback; +import android.net.wifi.rtt.RangingRequest; + +/** + * @hide + */ +interface IWifiRttManager +{ + void startRanging(in IBinder binder, in String callingPackage, in RangingRequest request, + in IRttCallback callback); +} diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.aidl b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl new file mode 100644 index 000000000000..8053c9416aeb --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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.rtt; + +parcelable RangingRequest; diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java new file mode 100644 index 000000000000..997b6800e97c --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2017 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.rtt; + +import android.net.wifi.ScanResult; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * Defines the ranging request to other devices. The ranging request is built using + * {@link RangingRequest.Builder}. + * A ranging request is executed using + * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. + * <p> + * The ranging request is a batch request - specifying a set of devices (specified using + * {@link RangingRequest.Builder#addAp(ScanResult)} and + * {@link RangingRequest.Builder#addAps(List)}). + * + * @hide RTT_API + */ +public final class RangingRequest implements Parcelable { + private static final int MAX_PEERS = 10; + + /** + * Returns the maximum number of peers to range which can be specified in a single {@code + * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g. + * through {@link RangingRequest.Builder#addAp(ScanResult)} or + * {@link RangingRequest.Builder#addAps(List)}. + * + * @return Maximum number of peers. + */ + public static int getMaxPeers() { + return MAX_PEERS; + } + + /** @hide */ + public final List<RttPeer> mRttPeers; + + /** @hide */ + private RangingRequest(List<RttPeer> rttPeers) { + mRttPeers = rttPeers; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeList(mRttPeers); + } + + public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() { + @Override + public RangingRequest[] newArray(int size) { + return new RangingRequest[size]; + } + + @Override + public RangingRequest createFromParcel(Parcel in) { + return new RangingRequest(in.readArrayList(null)); + } + }; + + /** @hide */ + @Override + public String toString() { + StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ","); + for (RttPeer rp : mRttPeers) { + sj.add(rp.toString()); + } + return sj.toString(); + } + + /** @hide */ + public void enforceValidity() { + if (mRttPeers.size() > MAX_PEERS) { + throw new IllegalArgumentException( + "Ranging to too many peers requested. Use getMaxPeers() API to get limit."); + } + } + + /** + * Builder class used to construct {@link RangingRequest} objects. + */ + public static final class Builder { + private List<RttPeer> mRttPeers = new ArrayList<>(); + + /** + * Add the device specified by the {@link ScanResult} to the list of devices with + * which to measure range. The total number of results added to a request cannot exceed the + * limit specified by {@link #getMaxPeers()}. + * + * @param apInfo Information of an Access Point (AP) obtained in a Scan Result. + * @return The builder to facilitate chaining + * {@code builder.setXXX(..).setXXX(..)}. + */ + public Builder addAp(ScanResult apInfo) { + if (apInfo == null) { + throw new IllegalArgumentException("Null ScanResult!"); + } + mRttPeers.add(new RttPeerAp(apInfo)); + return this; + } + + /** + * Add the devices specified by the {@link ScanResult}s to the list of devices with + * which to measure range. The total number of results added to a request cannot exceed the + * limit specified by {@link #getMaxPeers()}. + * + * @param apInfos Information of an Access Points (APs) obtained in a Scan Result. + * @return The builder to facilitate chaining + * {@code builder.setXXX(..).setXXX(..)}. + */ + public Builder addAps(List<ScanResult> apInfos) { + if (apInfos == null) { + throw new IllegalArgumentException("Null list of ScanResults!"); + } + for (ScanResult scanResult : apInfos) { + addAp(scanResult); + } + return this; + } + + /** + * Build {@link RangingRequest} given the current configurations made on the + * builder. + */ + public RangingRequest build() { + return new RangingRequest(mRttPeers); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof RangingRequest)) { + return false; + } + + RangingRequest lhs = (RangingRequest) o; + + return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers); + } + + @Override + public int hashCode() { + return mRttPeers.hashCode(); + } + + /** @hide */ + public interface RttPeer { + // empty (marker interface) + } + + /** @hide */ + public static class RttPeerAp implements RttPeer, Parcelable { + public final ScanResult scanResult; + + public RttPeerAp(ScanResult scanResult) { + this.scanResult = scanResult; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + scanResult.writeToParcel(dest, flags); + } + + public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() { + @Override + public RttPeerAp[] newArray(int size) { + return new RttPeerAp[size]; + } + + @Override + public RttPeerAp createFromParcel(Parcel in) { + return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in)); + } + }; + + @Override + public String toString() { + return new StringBuilder("RttPeerAp: scanResult=").append( + scanResult.toString()).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof RttPeerAp)) { + return false; + } + + RttPeerAp lhs = (RttPeerAp) o; + + // Note: the only thing which matters for the request identity is the BSSID of the AP + return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID); + } + + @Override + public int hashCode() { + return scanResult.hashCode(); + } + } +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.aidl b/wifi/java/android/net/wifi/rtt/RangingResult.aidl new file mode 100644 index 000000000000..ae295a610afa --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/RangingResult.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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.rtt; + +parcelable RangingResult; diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java new file mode 100644 index 000000000000..918803ef2018 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/RangingResult.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2017 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.rtt; + +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import libcore.util.HexEncoding; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Ranging result for a request started by + * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are + * returned in {@link RangingResultCallback#onRangingResults(List)}. + * <p> + * A ranging result is the distance measurement result for a single device specified in the + * {@link RangingRequest}. + * + * @hide RTT_API + */ +public final class RangingResult implements Parcelable { + private static final String TAG = "RangingResult"; + + private final int mStatus; + private final byte[] mMac; + private final int mDistanceCm; + private final int mDistanceStdDevCm; + private final int mRssi; + private final long mTimestamp; + + /** @hide */ + public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi, + long timestamp) { + mStatus = status; + mMac = mac; + mDistanceCm = distanceCm; + mDistanceStdDevCm = distanceStdDevCm; + mRssi = rssi; + mTimestamp = timestamp; + } + + /** + * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in + * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure. + */ + public int getStatus() { + return mStatus; + } + + /** + * @return The MAC address of the device whose range measurement was requested. Will correspond + * to the MAC address of the device in the {@link RangingRequest}. + * <p> + * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL. + */ + public byte[] getMacAddress() { + return mMac; + } + + /** + * @return The distance (in cm) to the device specified by {@link #getMacAddress()}. + * <p> + * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + */ + public int getDistanceCm() { + if (mStatus != RangingResultCallback.STATUS_SUCCESS) { + Log.e(TAG, "getDistanceCm(): invalid value retrieved"); + } + return mDistanceCm; + } + + /** + * @return The standard deviation of the measured distance (in cm) to the device specified by + * {@link #getMacAddress()}. The standard deviation is calculated over the measurements + * executed in a single RTT burst. + * <p> + * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + */ + public int getDistanceStdDevCm() { + if (mStatus != RangingResultCallback.STATUS_SUCCESS) { + Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved"); + } + return mDistanceStdDevCm; + } + + /** + * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement. + * <p> + * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + */ + public int getRssi() { + if (mStatus != RangingResultCallback.STATUS_SUCCESS) { + // TODO: should this be an exception? + Log.e(TAG, "getRssi(): invalid value retrieved"); + } + return mRssi; + } + + /** + * @return The timestamp (in us) at which the ranging operation was performed + * <p> + * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + */ + public long getRangingTimestamp() { + return mTimestamp; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeByteArray(mMac); + dest.writeInt(mDistanceCm); + dest.writeInt(mDistanceStdDevCm); + dest.writeInt(mRssi); + dest.writeLong(mTimestamp); + } + + /** @hide */ + public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() { + @Override + public RangingResult[] newArray(int size) { + return new RangingResult[size]; + } + + @Override + public RangingResult createFromParcel(Parcel in) { + int status = in.readInt(); + byte[] mac = in.createByteArray(); + int distanceCm = in.readInt(); + int distanceStdDevCm = in.readInt(); + int rssi = in.readInt(); + long timestamp = in.readLong(); + return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp); + } + }; + + /** @hide */ + @Override + public String toString() { + return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append( + mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append( + ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append( + mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append( + mTimestamp).append("]").toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof RangingResult)) { + return false; + } + + RangingResult lhs = (RangingResult) o; + + return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) + && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm + && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp; + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp); + } +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java new file mode 100644 index 000000000000..d7270ad29a84 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 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.rtt; + +import android.os.Handler; + +import java.util.List; + +/** + * Base class for ranging result callbacks. Should be extended by applications and set when calling + * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single + * result from a range request will be called in this object. + * + * @hide RTT_API + */ +public abstract class RangingResultCallback { + /** + * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging + * operation was successful and distance value is valid. + */ + public static final int STATUS_SUCCESS = 0; + + /** + * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging + * operation failed and the distance value is invalid. + */ + public static final int STATUS_FAIL = 1; + + /** + * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the + * devices specified in the request was attempted. + */ + public abstract void onRangingFailure(); + + /** + * Called when a ranging operation was executed. The list of results corresponds to devices + * specified in the ranging request. + * + * @param results List of range measurements, one per requested device. + */ + public abstract void onRangingResults(List<RangingResult> results); +} diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java new file mode 100644 index 000000000000..a085de17c035 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java @@ -0,0 +1,100 @@ +package android.net.wifi.rtt; + +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_WIFI_STATE; +import static android.Manifest.permission.CHANGE_WIFI_STATE; + +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +import java.util.List; + +/** + * This class provides the primary API for measuring distance (range) to other devices using the + * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology. + * <p> + * The devices which can be ranged include: + * <li>Access Points (APs) + * <p> + * Ranging requests are triggered using + * {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of + * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)} + * callback. + * + * @hide RTT_API + */ +@SystemService(Context.WIFI_RTT2_SERVICE) +public class WifiRttManager { + private static final String TAG = "WifiRttManager"; + private static final boolean VDBG = true; + + private final Context mContext; + private final IWifiRttManager mService; + + /** @hide */ + public WifiRttManager(Context context, IWifiRttManager service) { + mContext = context; + mService = service; + } + + /** + * Initiate a request to range to a set of devices specified in the {@link RangingRequest}. + * Results will be returned in the {@link RangingResultCallback} set of callbacks. + * + * @param request A request specifying a set of devices whose distance measurements are + * requested. + * @param callback A callback for the result of the ranging request. + * @param handler The Handler on whose thread to execute the callbacks of the {@code + * callback} object. If a null is provided then the application's main thread + * will be used. + */ + @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE}) + public void startRanging(RangingRequest request, RangingResultCallback callback, + @Nullable Handler handler) { + if (VDBG) { + Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler=" + + handler); + } + + Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper(); + Binder binder = new Binder(); + try { + mService.startRanging(binder, mContext.getOpPackageName(), request, + new RttCallbackProxy(looper, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class RttCallbackProxy extends IRttCallback.Stub { + private final Handler mHandler; + private final RangingResultCallback mCallback; + + RttCallbackProxy(Looper looper, RangingResultCallback callback) { + mHandler = new Handler(looper); + mCallback = callback; + } + + @Override + public void onRangingResults(int status, List<RangingResult> results) throws RemoteException { + if (VDBG) { + Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results=" + + results); + } + mHandler.post(() -> { + if (status == RangingResultCallback.STATUS_SUCCESS) { + mCallback.onRangingResults(results); + } else { + mCallback.onRangingFailure(); + } + }); + } + } +} diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java new file mode 100644 index 000000000000..23c75ce02e75 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017 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.rtt; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.wifi.ScanResult; +import android.os.Handler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.test.TestLooper; +import android.test.suitebuilder.annotation.SmallTest; + +import libcore.util.HexEncoding; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test harness for WifiRttManager class. + */ +@SmallTest +public class WifiRttManagerTest { + private WifiRttManager mDut; + private TestLooper mMockLooper; + private Handler mMockLooperHandler; + + private final String packageName = "some.package.name.for.rtt.app"; + + @Mock + public Context mockContext; + + @Mock + public IWifiRttManager mockRttService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mDut = new WifiRttManager(mockContext, mockRttService); + mMockLooper = new TestLooper(); + mMockLooperHandler = new Handler(mMockLooper.getLooper()); + + when(mockContext.getOpPackageName()).thenReturn(packageName); + } + + /** + * Validate ranging call flow with succesful results. + */ + @Test + public void testRangeSuccess() throws Exception { + RangingRequest request = new RangingRequest.Builder().build(); + List<RangingResult> results = new ArrayList<>(); + results.add(new RangingResult(RangingResultCallback.STATUS_SUCCESS, null, 15, 5, 10, 666)); + RangingResultCallback callbackMock = mock(RangingResultCallback.class); + ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class); + + // verify ranging request passed to service + mDut.startRanging(request, callbackMock, mMockLooperHandler); + verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request), + callbackCaptor.capture()); + + // service calls back with success + callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_SUCCESS, results); + mMockLooper.dispatchAll(); + verify(callbackMock).onRangingResults(results); + + verifyNoMoreInteractions(mockRttService, callbackMock); + } + + /** + * Validate ranging call flow which failed. + */ + @Test + public void testRangeFail() throws Exception { + RangingRequest request = new RangingRequest.Builder().build(); + RangingResultCallback callbackMock = mock(RangingResultCallback.class); + ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class); + + // verify ranging request passed to service + mDut.startRanging(request, callbackMock, mMockLooperHandler); + verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request), + callbackCaptor.capture()); + + // service calls back with failure code + callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_FAIL, null); + mMockLooper.dispatchAll(); + verify(callbackMock).onRangingFailure(); + + verifyNoMoreInteractions(mockRttService, callbackMock); + } + + /** + * Validate that RangingRequest parcel works (produces same object on write/read). + */ + @Test + public void testRangingRequestParcel() { + // Note: not validating parcel code of ScanResult (assumed to work) + ScanResult scanResult1 = new ScanResult(); + scanResult1.BSSID = "00:01:02:03:04:05"; + ScanResult scanResult2 = new ScanResult(); + scanResult2.BSSID = "06:07:08:09:0A:0B"; + ScanResult scanResult3 = new ScanResult(); + scanResult3.BSSID = "AA:BB:CC:DD:EE:FF"; + List<ScanResult> scanResults2and3 = new ArrayList<>(2); + scanResults2and3.add(scanResult2); + scanResults2and3.add(scanResult3); + + RangingRequest.Builder builder = new RangingRequest.Builder(); + builder.addAp(scanResult1); + builder.addAps(scanResults2and3); + RangingRequest request = builder.build(); + + Parcel parcelW = Parcel.obtain(); + request.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + RangingRequest rereadRequest = RangingRequest.CREATOR.createFromParcel(parcelR); + + assertEquals(request, rereadRequest); + } + + /** + * Validate that can request as many range operation as the upper limit on number of requests. + */ + @Test + public void testRangingRequestAtLimit() { + ScanResult scanResult = new ScanResult(); + List<ScanResult> scanResultList = new ArrayList<>(); + for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) { + scanResultList.add(scanResult); + } + + // create request + RangingRequest.Builder builder = new RangingRequest.Builder(); + builder.addAp(scanResult); + builder.addAps(scanResultList); + builder.addAp(scanResult); + RangingRequest request = builder.build(); + + // verify request + request.enforceValidity(); + } + + /** + * Validate that limit on number of requests is applied. + */ + @Test(expected = IllegalArgumentException.class) + public void testRangingRequestPastLimit() { + ScanResult scanResult = new ScanResult(); + List<ScanResult> scanResultList = new ArrayList<>(); + for (int i = 0; i < RangingRequest.getMaxPeers() - 1; ++i) { + scanResultList.add(scanResult); + } + + // create request + RangingRequest.Builder builder = new RangingRequest.Builder(); + builder.addAp(scanResult); + builder.addAps(scanResultList); + builder.addAp(scanResult); + RangingRequest request = builder.build(); + + // verify request + request.enforceValidity(); + } + + /** + * Validate that RangingResults parcel works (produces same object on write/read). + */ + @Test + public void testRangingResultsParcel() { + // Note: not validating parcel code of ScanResult (assumed to work) + int status = RangingResultCallback.STATUS_SUCCESS; + final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false); + int distanceCm = 105; + int distanceStdDevCm = 10; + int rssi = 5; + long timestamp = System.currentTimeMillis(); + + RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, + timestamp); + + Parcel parcelW = Parcel.obtain(); + result.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR); + + assertEquals(result, rereadResult); + } +} |
