Merge "Atom: Add BluetoothSocketConnectionStateChanged"
diff --git a/api/current.txt b/api/current.txt
index 0652fdd..64f3885 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30577,6 +30577,7 @@
method public int getNumSuccessfulMeasurements();
method @Nullable public android.net.wifi.aware.PeerHandle getPeerHandle();
method public long getRangingTimestampMillis();
+ method @Nullable public android.net.wifi.rtt.ResponderLocation getResponderLocation();
method public int getRssi();
method public int getStatus();
method public void writeToParcel(android.os.Parcel, int);
@@ -30594,6 +30595,64 @@
field public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2; // 0x2
}
+ public final class ResponderLocation implements android.os.Parcelable {
+ method public int describeContents();
+ method public double getAltitude();
+ method public int getAltitudeType();
+ method public double getAltitudeUncertainty();
+ method public java.util.List<android.net.MacAddress> getColocatedBssids();
+ method public int getDatum();
+ method public int getExpectedToMove();
+ method public double getLatitude();
+ method public double getLatitudeUncertainty();
+ method public int getLciFlags();
+ method public double getLongitude();
+ method public double getLongitudeUncertainty();
+ method public int getMapImageType();
+ method @Nullable public java.net.URL getMapImageUrl();
+ method public double getStaFloorNumber();
+ method public double getStaHeightAboveFloorMeters();
+ method public double getStaHeightAboveFloorUncertaintyMeters();
+ method public boolean isLciSubelementValid();
+ method public boolean isZsubelementValid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ALTITUDE_FLOORS = 2; // 0x2
+ field public static final int ALTITUDE_METERS = 1; // 0x1
+ field public static final int ALTITUDE_UNDEFINED = 0; // 0x0
+ field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.ResponderLocation> CREATOR;
+ field public static final int DATUM_NAD83_MLLW = 3; // 0x3
+ field public static final int DATUM_NAD83_NAV88 = 2; // 0x2
+ field public static final int DATUM_UNDEFINED = 0; // 0x0
+ field public static final int DATUM_WGS84 = 1; // 0x1
+ field public static final int LCI_FLAGS_MASK_DEPENDENT_STA = 4; // 0x4
+ field public static final int LCI_FLAGS_MASK_REGLOC_AGREEMENT = 16; // 0x10
+ field public static final int LCI_FLAGS_MASK_REGLOC_DSE = 8; // 0x8
+ field public static final int LCI_FLAGS_MASK_VERSION = 3; // 0x3
+ field public static final int LCI_VERSION_1 = 1; // 0x1
+ field public static final int LOCATION_FIXED = 0; // 0x0
+ field public static final int LOCATION_MOVEMENT_UNKNOWN = 2; // 0x2
+ field public static final int LOCATION_RESERVED = 3; // 0x3
+ field public static final int LOCATION_VARIABLE = 1; // 0x1
+ field public static final int MAP_TYPE_BMP = 12; // 0xc
+ field public static final int MAP_TYPE_CAD = 8; // 0x8
+ field public static final int MAP_TYPE_DWF = 7; // 0x7
+ field public static final int MAP_TYPE_DWG = 6; // 0x6
+ field public static final int MAP_TYPE_DXF = 5; // 0x5
+ field public static final int MAP_TYPE_GIF = 2; // 0x2
+ field public static final int MAP_TYPE_GML = 10; // 0xa
+ field public static final int MAP_TYPE_ICO = 17; // 0x11
+ field public static final int MAP_TYPE_JPG = 3; // 0x3
+ field public static final int MAP_TYPE_KML = 11; // 0xb
+ field public static final int MAP_TYPE_PGM = 13; // 0xd
+ field public static final int MAP_TYPE_PNG = 1; // 0x1
+ field public static final int MAP_TYPE_PPM = 14; // 0xe
+ field public static final int MAP_TYPE_SVG = 4; // 0x4
+ field public static final int MAP_TYPE_TIFF = 9; // 0x9
+ field public static final int MAP_TYPE_URL_DEFINED = 0; // 0x0
+ field public static final int MAP_TYPE_XBM = 15; // 0xf
+ field public static final int MAP_TYPE_XPM = 16; // 0x10
+ }
+
public class WifiRttManager {
method public boolean isAvailable();
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE}) public void startRanging(@NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8d98bc1..bf3c0a2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4943,6 +4943,10 @@
field public final boolean supports80211mc;
}
+ public final class ResponderLocation implements android.os.Parcelable {
+ method public boolean getExtraInfoOnAssociationIndication();
+ }
+
public class WifiRttManager {
method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE}) public void cancelRanging(@Nullable android.os.WorkSource);
method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE}) public void startRanging(@Nullable android.os.WorkSource, @NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b96aa2e..5f47e06 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -293,6 +293,7 @@
DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
NumBiometricsEnrolled num_faces_enrolled = 10048;
RoleHolder role_holder = 10049;
+ DangerousPermissionState dangerous_permission_state = 10050;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -5435,3 +5436,20 @@
optional string hosting_name = 9;
}
+/**
+ * State of a dangerous permission requested by a package
+ */
+message DangerousPermissionState {
+ // Name of the permission
+ optional string permission_name = 1;
+
+ // Uid of the package
+ optional int32 uid = 2 [(is_uid) = true];
+
+ // Package requesting the permission
+ optional string package_name = 3;
+
+ // If the permission is granted to the uid
+ optional bool is_granted = 4;
+}
+
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 6f3eeaa7..c69384c 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -223,6 +223,9 @@
// RoleHolder.
{android::util::ROLE_HOLDER,
{.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
+ // PermissionState.
+ {android::util::DANGEROUS_PERMISSION_STATE,
+ {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 20505ca..3c8e236 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -448,6 +448,8 @@
} finally {
execute(success ? "COMMIT" : "ROLLBACK", null, null);
}
+ } catch (SQLiteException ex) {
+ throw ex;
} catch (RuntimeException ex) {
throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
+ "' to '" + newLocale + "'.", ex);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index a1b0803..ae456af 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -890,9 +890,14 @@
try {
try {
openInner();
- } catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
- openInner();
+ } catch (RuntimeException ex) {
+ if (SQLiteDatabaseCorruptException.isCorruptException(ex)) {
+ Log.e(TAG, "Database corruption detected in open()", ex);
+ onCorruption();
+ openInner();
+ } else {
+ throw ex;
+ }
}
} catch (SQLiteException ex) {
Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java b/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java
index 73b6c0c..488ef46 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseCorruptException.java
@@ -25,4 +25,20 @@
public SQLiteDatabaseCorruptException(String error) {
super(error);
}
+
+ /**
+ * @return true if a given {@link Throwable} or any of its inner causes is of
+ * {@link SQLiteDatabaseCorruptException}.
+ *
+ * @hide
+ */
+ public static boolean isCorruptException(Throwable th) {
+ while (th != null) {
+ if (th instanceof SQLiteDatabaseCorruptException) {
+ return true;
+ }
+ th = th.getCause();
+ }
+ return false;
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 125dabe..b708ef1 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -268,7 +268,7 @@
* capture plane is too high.
*
* The tilt angle is defined as the angle swept out by the user’s face looking up
- * and down. The pan angle would be zero if the user faced the camera directly.
+ * and down. The tilt angle would be zero if the user faced the camera directly.
*
* The user should be informed to look more directly at the camera.
*/
@@ -279,8 +279,8 @@
* capture plane is too high.
*
* The roll angle is defined as the angle swept out by the user’s face tilting their head
- * towards their shoulders to the left and right. The pan angle would be zero if the user
- * faced the camera directly.
+ * towards their shoulders to the left and right. The roll angle would be zero if the user's
+ * head is vertically aligned with the camera.
*
* The user should be informed to look more directly at the camera.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9213f32..9a317db 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2289,6 +2289,7 @@
surfaceChanged |= surfaceSizeChanged;
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
+ final boolean colorModeChanged = hasColorModeChanged(lp.getColorMode());
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
@@ -2335,6 +2336,10 @@
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
+ if (colorModeChanged && mAttachInfo.mThreadedRenderer != null) {
+ mAttachInfo.mThreadedRenderer.setWideGamut(
+ lp.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
+ }
if (!hadSurface) {
if (mSurface.isValid()) {
@@ -2387,7 +2392,7 @@
mAttachInfo.mThreadedRenderer.destroy();
}
} else if ((surfaceGenerationId != mSurface.getGenerationId()
- || surfaceSizeChanged || windowRelayoutWasForced)
+ || surfaceSizeChanged || windowRelayoutWasForced || colorModeChanged)
&& mSurfaceHolder == null
&& mAttachInfo.mThreadedRenderer != null) {
mFullRedrawNeeded = true;
@@ -4009,6 +4014,20 @@
}
}
+ private boolean hasColorModeChanged(int colorMode) {
+ if (mAttachInfo.mThreadedRenderer == null) {
+ return false;
+ }
+ final boolean isWideGamut = colorMode == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+ if (mAttachInfo.mThreadedRenderer.isWideGamut() == isWideGamut) {
+ return false;
+ }
+ if (isWideGamut && !mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index c4ddd50..7ec76d7 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -154,6 +154,7 @@
private boolean mOpaque = true;
private boolean mForceDark = false;
private FrameInfo mScratchInfo;
+ private boolean mIsWideGamut = false;
/**
* Creates a new instance of a HardwareRenderer. The HardwareRenderer will default
@@ -498,6 +499,7 @@
* @hide
*/
public void setWideGamut(boolean wideGamut) {
+ mIsWideGamut = wideGamut;
nSetWideGamut(mNativeProxy, wideGamut);
}
@@ -673,6 +675,11 @@
nSetPictureCaptureCallback(mNativeProxy, callback);
}
+ /** @hide */
+ public boolean isWideGamut() {
+ return mIsWideGamut;
+ }
+
/** called by native */
static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
Picture picture = new Picture(picturePtr);
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 09d0a58..49d3530 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1245,6 +1245,9 @@
return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+ decoder.setOnPartialImageListener((e) -> {
+ return e.getError() == ImageDecoder.DecodeException.SOURCE_INCOMPLETE;
+ });
});
} catch (IOException e) {
/* do nothing.
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 5231486..68aa737 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -25,6 +25,8 @@
#include "hwui/PaintFilter.h"
+#include <SkFontMetrics.h>
+
namespace android {
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 8a35e70..873fbc2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -142,7 +142,7 @@
boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0;
if (isWakeDisplay) {
- onWakeScreen(wakeEvent);
+ onWakeScreen(wakeEvent, mMachine.getState());
} else if (isLongPress || isWakeLockScreen) {
requestPulse(pulseReason, sensorPerformedProxCheck);
} else {
@@ -195,9 +195,8 @@
}
}
- private void onWakeScreen(boolean wake) {
+ private void onWakeScreen(boolean wake, DozeMachine.State state) {
DozeLog.traceWakeDisplay(wake);
- DozeMachine.State state = mMachine.getState();
boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
sWakeDisplaySensorState = wake;
@@ -208,7 +207,7 @@
// In pocket, drop event.
return;
}
- if (mMachine.getState() == DozeMachine.State.DOZE) {
+ if (state == DozeMachine.State.DOZE) {
mMachine.requestState(DozeMachine.State.DOZE_AOD);
}
}, false /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP);
@@ -236,7 +235,7 @@
}
mDozeSensors.setListening(true);
if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
- onWakeScreen(false);
+ onWakeScreen(false, newState);
}
break;
case DOZE_AOD_PAUSED:
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 1124b7f..882a929 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -62,6 +62,7 @@
if (remaining == 0) {
mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier);
}
+ notifyUserActivity();
return sendEnrollResult(identifier, remaining);
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 2378c57..48e64338 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -15,6 +15,8 @@
*/
package com.android.server.stats;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.os.Process.getPidsForCommands;
import static android.os.Process.getUidForPid;
@@ -41,6 +43,7 @@
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.hardware.fingerprint.FingerprintManager;
import android.net.ConnectivityManager;
@@ -1793,6 +1796,65 @@
pulledData.add(e);
}
+ private void pullDangerousPermissionState(long elapsedNanos, final long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ PackageManager pm = mContext.getPackageManager();
+
+ List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+
+ int numUsers = users.size();
+ for (int userNum = 0; userNum < numUsers; userNum++) {
+ UserHandle user = users.get(userNum).getUserHandle();
+
+ List<PackageInfo> pkgs = pm.getInstalledPackagesAsUser(
+ PackageManager.GET_PERMISSIONS, user.getIdentifier());
+
+ int numPkgs = pkgs.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ PackageInfo pkg = pkgs.get(pkgNum);
+
+ if (pkg.requestedPermissions == null) {
+ continue;
+ }
+
+ int numPerms = pkg.requestedPermissions.length;
+ for (int permNum = 0; permNum < numPerms; permNum++) {
+ String permName = pkg.requestedPermissions[permNum];
+
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = pm.getPermissionInfo(permName, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ continue;
+ }
+
+ if (permissionInfo.getProtection() != PROTECTION_DANGEROUS) {
+ continue;
+ }
+
+ StatsLogEventWrapper e = new StatsLogEventWrapper(
+ StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos, wallClockNanos);
+
+ e.writeString(permName);
+ e.writeInt(pkg.applicationInfo.uid);
+ e.writeString(pkg.packageName);
+
+ e.writeBoolean((pkg.requestedPermissionsFlags[permNum]
+ & REQUESTED_PERMISSION_GRANTED) != 0);
+
+ pulledData.add(e);
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, "Could not read permissions", t);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Add a RoleHolder atom for each package that holds a role.
*
@@ -2025,6 +2087,10 @@
pullRoleHolders(elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.DANGEROUS_PERMISSION_STATE: {
+ pullDangerousPermissionState(elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index 061cd4b..6f84ec5 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -77,6 +77,14 @@
}
/**
+ * @hide
+ * @param ss signal strength from modem.
+ */
+ public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) {
+ this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr);
+ }
+
+ /**
* Reference: 3GPP TS 38.215.
* Range: -140 dBm to -44 dBm.
* @return SS reference signal received power, {@link CellInfo#UNAVAILABLE} means unreported
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 91375bc..d2ae106 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -85,6 +85,7 @@
CellSignalStrengthWcdma mWcdma;
CellSignalStrengthTdscdma mTdscdma;
CellSignalStrengthLte mLte;
+ CellSignalStrengthNr mNr;
/**
* Create a new SignalStrength from a intent notifier Bundle
@@ -116,7 +117,7 @@
public SignalStrength() {
this(new CellSignalStrengthCdma(), new CellSignalStrengthGsm(),
new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(),
- new CellSignalStrengthLte());
+ new CellSignalStrengthLte(), new CellSignalStrengthNr());
}
/**
@@ -129,12 +130,14 @@
@NonNull CellSignalStrengthGsm gsm,
@NonNull CellSignalStrengthWcdma wcdma,
@NonNull CellSignalStrengthTdscdma tdscdma,
- @NonNull CellSignalStrengthLte lte) {
+ @NonNull CellSignalStrengthLte lte,
+ @NonNull CellSignalStrengthNr nr) {
mCdma = cdma;
mGsm = gsm;
mWcdma = wcdma;
mTdscdma = tdscdma;
mLte = lte;
+ mNr = nr;
}
/**
@@ -147,7 +150,8 @@
new CellSignalStrengthGsm(signalStrength.gw),
new CellSignalStrengthWcdma(),
new CellSignalStrengthTdscdma(signalStrength.tdScdma),
- new CellSignalStrengthLte(signalStrength.lte));
+ new CellSignalStrengthLte(signalStrength.lte),
+ new CellSignalStrengthNr());
}
/**
@@ -160,7 +164,23 @@
new CellSignalStrengthGsm(signalStrength.gsm),
new CellSignalStrengthWcdma(signalStrength.wcdma),
new CellSignalStrengthTdscdma(signalStrength.tdScdma),
- new CellSignalStrengthLte(signalStrength.lte));
+ new CellSignalStrengthLte(signalStrength.lte),
+ new CellSignalStrengthNr());
+ }
+
+ /**
+ * Constructor for Radio HAL V1.4.
+ *
+ * @param signalStrength signal strength reported from modem.
+ * @hide
+ */
+ public SignalStrength(android.hardware.radio.V1_4.SignalStrength signalStrength) {
+ this(new CellSignalStrengthCdma(signalStrength.cdma, signalStrength.evdo),
+ new CellSignalStrengthGsm(signalStrength.gsm),
+ new CellSignalStrengthWcdma(signalStrength.wcdma),
+ new CellSignalStrengthTdscdma(signalStrength.tdscdma),
+ new CellSignalStrengthLte(signalStrength.lte),
+ new CellSignalStrengthNr(signalStrength.nr));
}
private CellSignalStrength getPrimary() {
@@ -171,6 +191,7 @@
if (mTdscdma.isValid()) return mTdscdma;
if (mWcdma.isValid()) return mWcdma;
if (mGsm.isValid()) return mGsm;
+ if (mNr.isValid()) return mNr;
return mLte;
}
@@ -200,6 +221,7 @@
if (mTdscdma.isValid()) cssList.add(mTdscdma);
if (mWcdma.isValid()) cssList.add(mWcdma);
if (mGsm.isValid()) cssList.add(mGsm);
+ if (mNr.isValid()) cssList.add(mNr);
return cssList;
}
@@ -210,6 +232,7 @@
mWcdma.updateLevel(cc, ss);
mTdscdma.updateLevel(cc, ss);
mLte.updateLevel(cc, ss);
+ mNr.updateLevel(cc, ss);
}
/**
@@ -234,6 +257,7 @@
mWcdma = new CellSignalStrengthWcdma(s.mWcdma);
mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
mLte = new CellSignalStrengthLte(s.mLte);
+ mNr = new CellSignalStrengthNr(s.mNr);
}
/**
@@ -250,6 +274,7 @@
mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader());
mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
+ mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
}
/**
@@ -261,6 +286,7 @@
out.writeParcelable(mWcdma, flags);
out.writeParcelable(mTdscdma, flags);
out.writeParcelable(mLte, flags);
+ out.writeParcelable(mNr, flags);
}
/**
@@ -814,7 +840,7 @@
*/
@Override
public int hashCode() {
- return Objects.hash(mCdma, mGsm, mWcdma, mTdscdma, mLte);
+ return Objects.hash(mCdma, mGsm, mWcdma, mTdscdma, mLte, mNr);
}
/**
@@ -830,7 +856,8 @@
&& mGsm.equals(s.mGsm)
&& mWcdma.equals(s.mWcdma)
&& mTdscdma.equals(s.mTdscdma)
- && mLte.equals(s.mLte);
+ && mLte.equals(s.mLte)
+ && mNr.equals(s.mNr);
}
/**
@@ -844,6 +871,7 @@
.append(",mWcdma=").append(mWcdma)
.append(",mTdscdma=").append(mTdscdma)
.append(",mLte=").append(mLte)
+ .append(",mNr=").append(mNr)
.append(",primary=").append(getPrimary().getClass().getSimpleName())
.append("}")
.toString();
@@ -866,6 +894,7 @@
mWcdma = m.getParcelable("Wcdma");
mTdscdma = m.getParcelable("Tdscdma");
mLte = m.getParcelable("Lte");
+ mNr = m.getParcelable("Nr");
}
/**
@@ -885,6 +914,7 @@
m.putParcelable("Wcdma", mWcdma);
m.putParcelable("Tdscdma", mTdscdma);
m.putParcelable("Lte", mLte);
+ m.putParcelable("Nr", mNr);
}
/**
diff --git a/telephony/java/com/android/internal/telephony/EncodeException.java b/telephony/java/com/android/internal/telephony/EncodeException.java
index 4e3fac1..cdc853e 100644
--- a/telephony/java/com/android/internal/telephony/EncodeException.java
+++ b/telephony/java/com/android/internal/telephony/EncodeException.java
@@ -22,6 +22,12 @@
* {@hide}
*/
public class EncodeException extends Exception {
+
+ private int mError = ERROR_UNENCODABLE;
+
+ public static final int ERROR_UNENCODABLE = 0;
+ public static final int ERROR_EXCEED_SIZE = 1;
+
public EncodeException() {
super();
}
@@ -31,9 +37,18 @@
super(s);
}
+ public EncodeException(String s, int error) {
+ super(s);
+ mError = error;
+ }
+
@UnsupportedAppUsage
public EncodeException(char c) {
super("Unencodable char: '" + c + "'");
}
+
+ public int getError() {
+ return mError;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index 84c0e64..a774cdc 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -388,7 +388,7 @@
* GSM extension table
* @return the encoded message
*
- * @throws EncodeException if String is too large to encode
+ * @throws EncodeException if String is too large to encode or any characters are unencodable
*/
@UnsupportedAppUsage
public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset,
@@ -402,7 +402,8 @@
}
septetCount += startingSeptetOffset;
if (septetCount > 255) {
- throw new EncodeException("Payload cannot exceed 255 septets");
+ throw new EncodeException(
+ "Payload cannot exceed 255 septets", EncodeException.ERROR_EXCEED_SIZE);
}
int byteCount = ((septetCount * 7) + 7) / 8;
byte[] ret = new byte[byteCount + 1]; // Include space for one byte length prefix.
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 4f5bfa9..015efa6 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -384,16 +384,22 @@
}
}
} catch (EncodeException ex) {
- // Encoding to the 7-bit alphabet failed. Let's see if we can
- // send it as a UCS-2 encoded message
- try {
- userData = encodeUCS2(message, header);
- encoding = ENCODING_16BIT;
- } catch(UnsupportedEncodingException uex) {
- Rlog.e(LOG_TAG,
- "Implausible UnsupportedEncodingException ",
- uex);
+ if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) {
+ Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex);
return null;
+ } else {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch (EncodeException ex1) {
+ Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1);
+ return null;
+ } catch (UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex);
+ return null;
+ }
}
}
@@ -438,9 +444,10 @@
*
* @return encoded message as UCS2
* @throws UnsupportedEncodingException
+ * @throws EncodeException if String is too large to encode
*/
private static byte[] encodeUCS2(String message, byte[] header)
- throws UnsupportedEncodingException {
+ throws UnsupportedEncodingException, EncodeException {
byte[] userData, textPart;
textPart = message.getBytes("utf-16be");
@@ -455,6 +462,10 @@
else {
userData = textPart;
}
+ if (userData.length > 255) {
+ throw new EncodeException(
+ "Payload cannot exceed 255 bytes", EncodeException.ERROR_EXCEED_SIZE);
+ }
byte[] ret = new byte[userData.length+1];
ret[0] = (byte) (userData.length & 0xff );
System.arraycopy(userData, 0, ret, 1, userData.length);
diff --git a/wifi/java/android/net/wifi/rtt/CivicLocation.java b/wifi/java/android/net/wifi/rtt/CivicLocation.java
new file mode 100644
index 0000000..a669d77
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/CivicLocation.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2018 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.annotation.Nullable;
+import android.location.Address;
+import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Parcelable.Creator;
+import android.util.SparseArray;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Decodes the Type Length Value (TLV) elements found in a Location Civic Record as defined by IEEE
+ * P802.11-REVmc/D8.0 section 9.4.2.22.13 using the format described in IETF RFC 4776.
+ *
+ * <p>The TLVs each define a key, value pair for a civic address type such as apt, street, city,
+ * county, and country. The class provides a general getter method to extract a value for an element
+ * key, returning null if not set.
+ *
+ * @hide
+ */
+public final class CivicLocation implements Parcelable {
+ // Address (class) line indexes
+ private static final int ADDRESS_LINE_0_ROOM_DESK_FLOOR = 0;
+ private static final int ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT = 1;
+ private static final int ADDRESS_LINE_2_CITY = 2;
+ private static final int ADDRESS_LINE_3_STATE_POSTAL_CODE = 3;
+ private static final int ADDRESS_LINE_4_COUNTRY = 4;
+
+ // Buffer management
+ private static final int MIN_CIVIC_BUFFER_SIZE = 3;
+ private static final int MAX_CIVIC_BUFFER_SIZE = 256;
+ private static final int COUNTRY_CODE_LENGTH = 2;
+ private static final int BYTE_MASK = 0xFF;
+ private static final int TLV_TYPE_INDEX = 0;
+ private static final int TLV_LENGTH_INDEX = 1;
+ private static final int TLV_VALUE_INDEX = 2;
+
+ private final boolean mIsValid;
+ private final String mCountryCode; // Two character country code (ISO 3166 standard).
+ private SparseArray<String> mCivicAddressElements =
+ new SparseArray<>(MIN_CIVIC_BUFFER_SIZE);
+
+
+ /**
+ * Constructor
+ *
+ * @param civicTLVs a byte buffer containing parameters in the form type, length, value
+ * @param countryCode the two letter code defined by the ISO 3166 standard
+ *
+ * @hide
+ */
+ public CivicLocation(@Nullable byte[] civicTLVs, @Nullable String countryCode) {
+ this.mCountryCode = countryCode;
+ if (countryCode == null || countryCode.length() != COUNTRY_CODE_LENGTH) {
+ this.mIsValid = false;
+ return;
+ }
+ boolean isValid = false;
+ if (civicTLVs != null
+ && civicTLVs.length >= MIN_CIVIC_BUFFER_SIZE
+ && civicTLVs.length < MAX_CIVIC_BUFFER_SIZE) {
+ isValid = parseCivicTLVs(civicTLVs);
+ }
+
+ mIsValid = isValid;
+ }
+
+ private CivicLocation(Parcel in) {
+ mIsValid = in.readByte() != 0;
+ mCountryCode = in.readString();
+ mCivicAddressElements = in.readSparseArray(this.getClass().getClassLoader());
+ }
+
+ public static final Creator<CivicLocation> CREATOR = new Creator<CivicLocation>() {
+ @Override
+ public CivicLocation createFromParcel(Parcel in) {
+ return new CivicLocation(in);
+ }
+
+ @Override
+ public CivicLocation[] newArray(int size) {
+ return new CivicLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByte((byte) (mIsValid ? 1 : 0));
+ parcel.writeString(mCountryCode);
+ parcel.writeSparseArray((android.util.SparseArray) mCivicAddressElements);
+ }
+
+ /**
+ * Check TLV format and store TLV key/value pairs in this object so they can be queried by key.
+ *
+ * @param civicTLVs the buffer of TLV elements
+ * @return a boolean indicating success of the parsing process
+ */
+ private boolean parseCivicTLVs(byte[] civicTLVs) {
+ int bufferPtr = 0;
+ int bufferLength = civicTLVs.length;
+
+ // Iterate through the sub-elements contained in the LCI IE checking the accumulated
+ // element lengths do not overflow the total buffer length
+ while (bufferPtr < bufferLength) {
+ int civicAddressType = civicTLVs[bufferPtr + TLV_TYPE_INDEX] & BYTE_MASK;
+ int civicAddressTypeLength = civicTLVs[bufferPtr + TLV_LENGTH_INDEX];
+ if (civicAddressTypeLength != 0) {
+ if (bufferPtr + TLV_VALUE_INDEX + civicAddressTypeLength > bufferLength) {
+ return false;
+ }
+ mCivicAddressElements.put(civicAddressType,
+ new String(civicTLVs, bufferPtr + TLV_VALUE_INDEX,
+ civicAddressTypeLength, StandardCharsets.UTF_8));
+ }
+ bufferPtr += civicAddressTypeLength + TLV_VALUE_INDEX;
+ }
+ return true;
+ }
+
+ /**
+ * Getter for the value of a civic Address element type.
+ *
+ * @param key an integer code for the element type key
+ * @return the string value associated with that element type
+ */
+ @Nullable
+ public String getCivicElementValue(@CivicLocationKeysType int key) {
+ return mCivicAddressElements.get(key);
+ }
+
+ /**
+ * Generates a comma separated string of all the defined elements.
+ *
+ * @return a compiled string representing all elements
+ */
+ @Override
+ public String toString() {
+ return mCivicAddressElements.toString();
+ }
+
+ /**
+ * Converts Civic Location to the best effort Address Object.
+ *
+ * @return the {@link Address} object based on the Civic Location data
+ */
+ @Nullable
+ public Address toAddress() {
+ if (!mIsValid) {
+ return null;
+ }
+ Address address = new Address(Locale.US);
+ String room = formatAddressElement("Room: ", getCivicElementValue(CivicLocationKeys.ROOM));
+ String desk =
+ formatAddressElement(" Desk: ", getCivicElementValue(CivicLocationKeys.DESK));
+ String floor =
+ formatAddressElement(", Flr: ", getCivicElementValue(CivicLocationKeys.FLOOR));
+ String houseNumber = formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNO));
+ String houseNumberSuffix =
+ formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNS));
+ String road =
+ formatAddressElement(" ", getCivicElementValue(
+ CivicLocationKeys.PRIMARY_ROAD_NAME));
+ String roadSuffix = formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.STS));
+ String apt = formatAddressElement(", Apt: ", getCivicElementValue(CivicLocationKeys.APT));
+ String city = formatAddressElement("", getCivicElementValue(CivicLocationKeys.CITY));
+ String state = formatAddressElement("", getCivicElementValue(CivicLocationKeys.STATE));
+ String postalCode =
+ formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
+
+ // Aggregation into common address format
+ String addressLine0 =
+ new StringBuilder().append(room).append(desk).append(floor).toString();
+ String addressLine1 =
+ new StringBuilder().append(houseNumber).append(houseNumberSuffix).append(road)
+ .append(roadSuffix).append(apt).toString();
+ String addressLine2 = city;
+ String addressLine3 = new StringBuilder().append(state).append(postalCode).toString();
+ String addressLine4 = mCountryCode;
+
+ // Setting Address object line fields by common convention.
+ address.setAddressLine(ADDRESS_LINE_0_ROOM_DESK_FLOOR, addressLine0);
+ address.setAddressLine(ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT, addressLine1);
+ address.setAddressLine(ADDRESS_LINE_2_CITY, addressLine2);
+ address.setAddressLine(ADDRESS_LINE_3_STATE_POSTAL_CODE, addressLine3);
+ address.setAddressLine(ADDRESS_LINE_4_COUNTRY, addressLine4);
+
+ // Other compatible fields between the CIVIC_ADDRESS and the Address Class.
+ address.setFeatureName(getCivicElementValue(CivicLocationKeys.NAM)); // Structure name
+ address.setSubThoroughfare(getCivicElementValue(CivicLocationKeys.HNO));
+ address.setThoroughfare(getCivicElementValue(CivicLocationKeys.PRIMARY_ROAD_NAME));
+ address.setSubLocality(getCivicElementValue(CivicLocationKeys.NEIGHBORHOOD));
+ address.setSubAdminArea(getCivicElementValue(CivicLocationKeys.COUNTY));
+ address.setAdminArea(getCivicElementValue(CivicLocationKeys.STATE));
+ address.setPostalCode(getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
+ address.setCountryCode(mCountryCode); // Country
+ return address;
+ }
+
+ /**
+ * Prepares an address element so that it can be integrated into an address line convention.
+ *
+ * <p>If an address element is null, the return string will be empty e.g. "".
+ *
+ * @param label a string defining the type of address element
+ * @param value a string defining the elements value
+ * @return the formatted version of the value, with null values converted to empty strings
+ */
+ private String formatAddressElement(String label, String value) {
+ if (value != null) {
+ return label + value;
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CivicLocation)) {
+ return false;
+ }
+ CivicLocation other = (CivicLocation) obj;
+ return mIsValid == other.mIsValid
+ && Objects.equals(mCountryCode, other.mCountryCode)
+ && isSparseArrayStringEqual(mCivicAddressElements, other.mCivicAddressElements);
+ }
+
+ @Override
+ public int hashCode() {
+ int[] civicAddressKeys = getSparseArrayKeys(mCivicAddressElements);
+ String[] civicAddressValues = getSparseArrayValues(mCivicAddressElements);
+ return Objects.hash(mIsValid, mCountryCode, civicAddressKeys, civicAddressValues);
+ }
+
+ /**
+ * Tests if the Civic Location object is valid
+ *
+ * @return a boolean defining mIsValid
+ */
+ public boolean isValid() {
+ return mIsValid;
+ }
+
+ /**
+ * Tests if two sparse arrays are equal on a key for key basis
+ *
+ * @param sa1 the first sparse array
+ * @param sa2 the second sparse array
+ * @return the boolean result after comparing values key by key
+ */
+ private boolean isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2) {
+ int size = sa1.size();
+ if (size != sa2.size()) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ String sa1Value = sa1.valueAt(i);
+ String sa2Value = sa2.valueAt(i);
+ if (!sa1Value.equals(sa2Value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Extract an array of all the keys in a SparseArray<String>
+ *
+ * @param sa the sparse array of Strings
+ * @return an integer array of all keys in the SparseArray<String>
+ */
+ private int[] getSparseArrayKeys(SparseArray<String> sa) {
+ int size = sa.size();
+ int[] keys = new int[size];
+ for (int i = 0; i < size; i++) {
+ keys[i] = sa.keyAt(i);
+ }
+ return keys;
+ }
+
+ /**
+ * Extract an array of all the String values in a SparseArray<String>
+ *
+ * @param sa the sparse array of Strings
+ * @return a String array of all values in the SparseArray<String>
+ */
+ private String[] getSparseArrayValues(SparseArray<String> sa) {
+ int size = sa.size();
+ String[] values = new String[size];
+ for (int i = 0; i < size; i++) {
+ values[i] = sa.valueAt(i);
+ }
+ return values;
+ }
+}
diff --git a/wifi/java/android/net/wifi/rtt/CivicLocationKeys.java b/wifi/java/android/net/wifi/rtt/CivicLocationKeys.java
new file mode 100644
index 0000000..17c3671
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/CivicLocationKeys.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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 java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Civic Address key types used to define address elements.
+ *
+ * <p>These keys can be used in ResponderLocation look-up the corresponding string values.</p>
+ *
+ * @hide
+ */
+public class CivicLocationKeys {
+
+ /**
+ * An enumeration of all civic location keys.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({LANGUAGE, STATE, COUNTY, CITY, BOROUGH, NEIGHBORHOOD, GROUP_OF_STREETS, PRD, POD, STS,
+ HNO, HNS, LMK, LOC, NAM, POSTAL_CODE, BUILDING, APT, FLOOR, ROOM, TYPE_OF_PLACE, PCN,
+ PO_BOX, ADDITIONAL_CODE, DESK, PRIMARY_ROAD_NAME, ROAD_SECTION, BRANCH_ROAD_NAME,
+ SUBBRANCH_ROAD_NAME, STREET_NAME_PRE_MODIFIER, STREET_NAME_POST_MODIFIER, SCRIPT})
+ public @interface CivicLocationKeysType {
+ }
+
+ /** Language key e.g. i-default. */
+ public static final int LANGUAGE = 0;
+ /** Category label A1 key e.g. California. */
+ public static final int STATE = 1;
+ /** Category label A2 key e.g. Marin. */
+ public static final int COUNTY = 2;
+ /** Category label A3 key e.g. San Francisco. */
+ public static final int CITY = 3;
+ /** Category label A4 key e.g. Westminster. */
+ public static final int BOROUGH = 4;
+ /** Category label A5 key e.g. Pacific Heights. */
+ public static final int NEIGHBORHOOD = 5;
+ /** Category label A6 key e.g. University District. */
+ public static final int GROUP_OF_STREETS = 6;
+ // 7 - 15 not defined
+ /** Leading Street direction key e.g. N. */
+ public static final int PRD = 16;
+ /** Trailing street suffix key e.g. SW. */
+ public static final int POD = 17;
+ /** Street suffix or Type key e.g Ave, Platz. */
+ public static final int STS = 18;
+ /** House Number key e.g. 123. */
+ public static final int HNO = 19;
+ /** House number suffix key e.g. A, 1/2. */
+ public static final int HNS = 20;
+ /** Landmark or vanity address key e.g. Columbia Univ. */
+ public static final int LMK = 21;
+ /** Additional Location info key e.g. South Wing. */
+ public static final int LOC = 22;
+ /** Name of residence key e.g. Joe's Barbershop. */
+ public static final int NAM = 23;
+ /** Postal or ZIP code key e.g. 10027-1234. */
+ public static final int POSTAL_CODE = 24;
+ /** Building key e.g. Low Library. */
+ public static final int BUILDING = 25;
+ /** Apartment or suite key e.g. Apt 42. */
+ public static final int APT = 26;
+ /** Floor key e.g. 4. */
+ public static final int FLOOR = 27;
+ /** Room key e.g. 450F. */
+ public static final int ROOM = 28;
+ /** Type of place key e.g. office. */
+ public static final int TYPE_OF_PLACE = 29;
+ /** Postal community name key e.g. Leonia. */
+ public static final int PCN = 30;
+ /** Post Office Box key e.g. 12345. */
+ public static final int PO_BOX = 31;
+ /** Additional Code key e.g. 13203000003. */
+ public static final int ADDITIONAL_CODE = 32;
+ /** Seat, desk, pole, or cubical key e.g. WS 181. */
+ public static final int DESK = 33;
+ /** Primary road name key e.g. Shoreline. */
+ public static final int PRIMARY_ROAD_NAME = 34;
+ /** Road Section key e.g. 14. */
+ public static final int ROAD_SECTION = 35;
+ /** Branch Rd Name key e.g. Lane 7. */
+ public static final int BRANCH_ROAD_NAME = 36;
+ /** Subbranch Rd Name key e.g. Alley 8. */
+ public static final int SUBBRANCH_ROAD_NAME = 37;
+ /** Premodifier key e.g. Old. */
+ public static final int STREET_NAME_PRE_MODIFIER = 38;
+ /** Postmodifier key e.g. Service. */
+ public static final int STREET_NAME_POST_MODIFIER = 39;
+ /** Script key e.g. Latn. */
+ public static final int SCRIPT = 128;
+
+ /** private constructor */
+ private CivicLocationKeys() {}
+}
+
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
index 758a8d5..b53b35c 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResult.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -82,12 +82,14 @@
private final int mNumSuccessfulMeasurements;
private final byte[] mLci;
private final byte[] mLcr;
+ private final ResponderLocation mResponderLocation;
private final long mTimestamp;
/** @hide */
public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
- int numSuccessfulMeasurements, byte[] lci, byte[] lcr, long timestamp) {
+ int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
+ ResponderLocation responderLocation, long timestamp) {
mStatus = status;
mMac = mac;
mPeerHandle = null;
@@ -98,13 +100,15 @@
mNumSuccessfulMeasurements = numSuccessfulMeasurements;
mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
+ mResponderLocation = responderLocation;
mTimestamp = timestamp;
}
/** @hide */
public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
- int numSuccessfulMeasurements, byte[] lci, byte[] lcr, long timestamp) {
+ int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
+ ResponderLocation responderLocation, long timestamp) {
mStatus = status;
mMac = null;
mPeerHandle = peerHandle;
@@ -115,6 +119,7 @@
mNumSuccessfulMeasurements = numSuccessfulMeasurements;
mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
+ mResponderLocation = responderLocation;
mTimestamp = timestamp;
}
@@ -240,6 +245,24 @@
}
/**
+ * @return The responder location represented as {@link ResponderLocation} which captures
+ * location information the responder is programmed to broadcast.
+ * <p>
+ * Will return a {@code null} when the location information cannot be parsed.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ @Nullable
+ public ResponderLocation getResponderLocation() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getResponderLocation(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mResponderLocation;
+ }
+
+ /**
* @return The Location Configuration Information (LCI) as self-reported by the peer. The format
* is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.10.
* <p>
@@ -322,6 +345,7 @@
dest.writeInt(mNumSuccessfulMeasurements);
dest.writeByteArray(mLci);
dest.writeByteArray(mLcr);
+ dest.writeParcelable(mResponderLocation, flags);
dest.writeLong(mTimestamp);
}
@@ -351,13 +375,17 @@
int numSuccessfulMeasurements = in.readInt();
byte[] lci = in.createByteArray();
byte[] lcr = in.createByteArray();
+ ResponderLocation responderLocation =
+ in.readParcelable(this.getClass().getClassLoader());
long timestamp = in.readLong();
if (peerHandlePresent) {
return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
+ responderLocation, timestamp);
} else {
return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
+ responderLocation, timestamp);
}
}
};
@@ -372,7 +400,8 @@
", rssi=").append(mRssi).append(", numAttemptedMeasurements=").append(
mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
- mLcr).append(", timestamp=").append(mTimestamp).append("]").toString();
+ mLcr).append(", responderLocation=").append(mResponderLocation)
+ .append(", timestamp=").append(mTimestamp).append("]").toString();
}
@Override
@@ -393,12 +422,14 @@
&& mNumAttemptedMeasurements == lhs.mNumAttemptedMeasurements
&& mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
&& Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
- && mTimestamp == lhs.mTimestamp;
+ && mTimestamp == lhs.mTimestamp
+ && Objects.equals(mResponderLocation, lhs.mResponderLocation);
}
@Override
public int hashCode() {
return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
- mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr, mTimestamp);
+ mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
+ mResponderLocation, mTimestamp);
}
}
diff --git a/wifi/java/android/net/wifi/rtt/ResponderLocation.java b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
new file mode 100644
index 0000000..2912a67
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/ResponderLocation.java
@@ -0,0 +1,1356 @@
+/*
+ * Copyright (C) 2018 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 java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.location.Address;
+import android.net.MacAddress;
+import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ResponderLocation is both a Location Configuration Information (LCI) decoder and a Location Civic
+ * Report (LCR) decoder for information received from a Wi-Fi Access Point (AP) during Wi-Fi RTT
+ * ranging process.
+ *
+ * <p>This is based on the IEEE P802.11-REVmc/D8.0 spec section 9.4.2.22, under Measurement Report
+ * Element. Subelement location data-fields parsed from separate input LCI and LCR Information
+ * Elements are unified in this class.</p>
+ *
+ * <p>Note: The information provided by this class is broadcast by a responder (usually an Access
+ * Point), and passed on as-is. There is no guarantee this information is accurate or correct, and
+ * as a result developers should carefully consider how this information should be used and provide
+ * corresponding advice to users.</p>
+ */
+public final class ResponderLocation implements Parcelable {
+ private static final int BYTE_MASK = 0xFF;
+ private static final int LSB_IN_BYTE = 0x01;
+ private static final int MSB_IN_BYTE = 0x80;
+ private static final int MIN_BUFFER_SIZE = 3; // length of LEAD_LCI_ELEMENT_BYTES
+ private static final int MAX_BUFFER_SIZE = 256;
+
+ // Information Element (IE) fields
+ private static final byte MEASUREMENT_TOKEN_AUTONOMOUS = 0x01;
+ private static final byte MEASUREMENT_REPORT_MODE = 0x00;
+ private static final byte MEASUREMENT_TYPE_LCI = 0x08;
+ private static final byte MEASUREMENT_TYPE_LCR = 0x0b;
+
+ // LCI Subelement IDs
+ private static final byte SUBELEMENT_LCI = 0x00;
+ private static final byte SUBELEMENT_Z = 0x04;
+ private static final byte SUBELEMENT_USAGE = 0x06;
+ private static final byte SUBELEMENT_BSSID_LIST = 0x07;
+
+ // LCI Subelement Lengths
+ private static final int SUBELEMENT_LCI_LENGTH = 16;
+ private static final int SUBELEMENT_Z_LENGTH = 6;
+ private static final int SUBELEMENT_USAGE_LENGTH1 = 1;
+ private static final int SUBELEMENT_USAGE_LENGTH3 = 3;
+ private static final int SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH = 1;
+
+ private static final byte[] LEAD_LCI_ELEMENT_BYTES = {
+ MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCI
+ };
+
+ // Subelement LCI constants
+
+ /* The LCI subelement bit-field lengths are described in Figure 9-214 of the REVmc spec and
+ represented here as a an array of integers */
+ private static final int[] SUBELEMENT_LCI_BIT_FIELD_LENGTHS = {
+ 6, 34, 6, 34, 4, 6, 30, 3, 1, 1, 1, 2
+ };
+ private static final int LATLNG_FRACTION_BITS = 25;
+ private static final int LATLNG_UNCERTAINTY_BASE = 8;
+ private static final int ALTITUDE_FRACTION_BITS = 8;
+ private static final int ALTITUDE_UNCERTAINTY_BASE = 21;
+ private static final double LAT_ABS_LIMIT = 90.0;
+ private static final double LNG_ABS_LIMIT = 180.0;
+ private static final int UNCERTAINTY_UNDEFINED = 0;
+
+ // Subelement LCI fields indices
+ private static final int SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX = 0;
+ private static final int SUBELEMENT_LCI_LAT_INDEX = 1;
+ private static final int SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX = 2;
+ private static final int SUBELEMENT_LCI_LNG_INDEX = 3;
+ private static final int SUBELEMENT_LCI_ALT_TYPE_INDEX = 4;
+ private static final int SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX = 5;
+ private static final int SUBELEMENT_LCI_ALT_INDEX = 6;
+ private static final int SUBELEMENT_LCI_DATUM_INDEX = 7;
+ private static final int SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX = 8;
+ private static final int SUBELEMENT_LCI_REGLOC_DSE_INDEX = 9;
+ private static final int SUBELEMENT_LCI_DEPENDENT_STA_INDEX = 10;
+ private static final int SUBELEMENT_LCI_VERSION_INDEX = 11;
+
+ /**
+ * The Altitude value is interpreted based on the Altitude Type, and the selected mDatum.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({ALTITUDE_UNDEFINED, ALTITUDE_METERS, ALTITUDE_FLOORS})
+ public @interface AltitudeType {
+ }
+
+ /**
+ * Altitude is not defined for the Responder.
+ * The altitude and altitude uncertainty should not be used: see section 2.4 of IETF RFC 6225.
+ */
+ public static final int ALTITUDE_UNDEFINED = 0;
+ /** Responder Altitude is measured in meters. */
+ public static final int ALTITUDE_METERS = 1;
+ /** Responder Altitude is measured in floors. */
+ public static final int ALTITUDE_FLOORS = 2;
+
+ /**
+ * The Datum value determines how coordinates are organized in relation to the real world.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({DATUM_UNDEFINED, DATUM_WGS84, DATUM_NAD83_NAV88, DATUM_NAD83_MLLW})
+ public @interface DatumType {
+ }
+
+ /** Datum is not defined. */
+ public static final int DATUM_UNDEFINED = 0;
+ /** Datum used is WGS84. */
+ public static final int DATUM_WGS84 = 1;
+ /** Datum used is NAD83 NAV88. */
+ public static final int DATUM_NAD83_NAV88 = 2;
+ /** Datum used is NAD83 MLLW. */
+ public static final int DATUM_NAD83_MLLW = 3;
+
+
+ /** Version of the LCI protocol is 1.0, the only defined protocol at this time. */
+ public static final int LCI_VERSION_1 = 1;
+
+ /**
+ * Enumerates the flags contained in getLciFlags()
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef(flag = true, value = {LCI_FLAGS_MASK_REGLOC_AGREEMENT, LCI_FLAGS_MASK_REGLOC_DSE,
+ LCI_FLAGS_MASK_DEPENDENT_STA, LCI_FLAGS_MASK_VERSION})
+ public @interface LciFlagMasks {
+ }
+ /** Location agreement flag is obtained by ANDing this mask with the getLciFlags() value.*/
+ public static final int LCI_FLAGS_MASK_REGLOC_AGREEMENT = 0x10;
+ /** Location DSE flag is obtained by ANDing this mask with the getLciFlags() value.*/
+ public static final int LCI_FLAGS_MASK_REGLOC_DSE = 0x08;
+ /** Dependent station flag is obtained by ANDing this mask with the getLciFlags() value. */
+ public static final int LCI_FLAGS_MASK_DEPENDENT_STA = 0x04;
+ /** Version bits are obtained by ANDing this mask with the getLciFlags() value.*/
+ public static final int LCI_FLAGS_MASK_VERSION = 0x03;
+
+ // LCI Subelement Z constants
+ private static final int[] SUBELEMENT_Z_BIT_FIELD_LENGTHS = {2, 14, 24, 8};
+ private static final int Z_FLOOR_NUMBER_FRACTION_BITS = 4;
+ private static final int Z_FLOOR_HEIGHT_FRACTION_BITS = 12;
+ private static final int Z_MAX_HEIGHT_UNCERTAINTY_FACTOR = 25;
+
+ // LCI Subelement Z fields indices
+ private static final int SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX = 0;
+ private static final int SUBELEMENT_Z_STA_FLOOR_NUMBER_INDEX = 1;
+ private static final int SUBELEMENT_Z_STA_HEIGHT_ABOVE_FLOOR_INDEX = 2;
+ private static final int SUBELEMENT_Z_STA_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX = 3;
+
+ // LCI Subelement Usage Rules constants
+ private static final int SUBELEMENT_USAGE_MASK_RETRANSMIT = 0x01;
+ private static final int SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES = 0x02;
+ private static final int SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY = 0x04;
+
+ // LCI Subelement Usage Rules field indices
+ private static final int SUBELEMENT_USAGE_PARAMS_INDEX = 0; // 8 bits
+
+ // LCI Subelement BSSID List
+ private static final int SUBELEMENT_BSSID_MAX_INDICATOR_INDEX = 0;
+ private static final int SUBELEMENT_BSSID_LIST_INDEX = 1;
+ private static final int BYTES_IN_A_BSSID = 6;
+
+ /**
+ * The Expected-To-Move value determines how mobile we expect the STA to be.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({LOCATION_FIXED, LOCATION_VARIABLE, LOCATION_MOVEMENT_UNKNOWN, LOCATION_RESERVED})
+ public @interface ExpectedToMoveType {
+ }
+
+ /** Location of responder is fixed (does not move) */
+ public static final int LOCATION_FIXED = 0;
+ /** Location of the responder is variable, and may move */
+ public static final int LOCATION_VARIABLE = 1;
+ /** Location of the responder is not known to be either fixed or variable. */
+ public static final int LOCATION_MOVEMENT_UNKNOWN = 2;
+ /** Location of the responder status is a reserved value */
+ public static final int LOCATION_RESERVED = 3;
+
+ // LCR Subelement IDs
+ private static final byte SUBELEMENT_LOCATION_CIVIC = 0x00;
+ private static final byte SUBELEMENT_MAP_IMAGE = 0x05;
+
+ // LCR Subelement Lengths
+ private static final int SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH = 2;
+ private static final int SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH = 256;
+ private static final int SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH = 256;
+
+ private static final byte[] LEAD_LCR_ELEMENT_BYTES = {
+ MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCR
+ };
+
+ // LCR Location Civic Subelement
+ private static final int CIVIC_COUNTRY_CODE_INDEX = 0;
+ private static final int CIVIC_TLV_LIST_INDEX = 2;
+
+ // LCR Map Image Subelement field indexes.
+ private static final int SUBELEMENT_IMAGE_MAP_TYPE_INDEX = 0;
+
+ /**
+ * The Map Type value specifies the image format type.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({
+ MAP_TYPE_URL_DEFINED,
+ MAP_TYPE_PNG,
+ MAP_TYPE_GIF,
+ MAP_TYPE_JPG,
+ MAP_TYPE_SVG,
+ MAP_TYPE_DXF,
+ MAP_TYPE_DWG,
+ MAP_TYPE_DWF,
+ MAP_TYPE_CAD,
+ MAP_TYPE_TIFF,
+ MAP_TYPE_GML,
+ MAP_TYPE_KML,
+ MAP_TYPE_BMP,
+ MAP_TYPE_PGM,
+ MAP_TYPE_PPM,
+ MAP_TYPE_XBM,
+ MAP_TYPE_XPM,
+ MAP_TYPE_ICO
+ })
+ public @interface MapImageType {
+ }
+
+ /** File type defined by the file suffix itself. */
+ public static final int MAP_TYPE_URL_DEFINED = 0;
+ /** File type is in PNG format. */
+ public static final int MAP_TYPE_PNG = 1;
+ /** File type is in GIF format. */
+ public static final int MAP_TYPE_GIF = 2;
+ /** File type is in JPG format. */
+ public static final int MAP_TYPE_JPG = 3;
+ /** File type is in SVG format. */
+ public static final int MAP_TYPE_SVG = 4;
+ /** File type is in DXF format. */
+ public static final int MAP_TYPE_DXF = 5;
+ /** File type is in DWG format. */
+ public static final int MAP_TYPE_DWG = 6;
+ /** File type is in DWF format. */
+ public static final int MAP_TYPE_DWF = 7;
+ /** File type is in CAD format. */
+ public static final int MAP_TYPE_CAD = 8;
+ /** File type is in TIFF format. */
+ public static final int MAP_TYPE_TIFF = 9;
+ /** File type is in GML format. */
+ public static final int MAP_TYPE_GML = 10;
+ /** File type is in KML format. */
+ public static final int MAP_TYPE_KML = 11;
+ /** File type is in BMP format. */
+ public static final int MAP_TYPE_BMP = 12;
+ /** File type is in PGM format. */
+ public static final int MAP_TYPE_PGM = 13;
+ /** File type is in PPM format. */
+ public static final int MAP_TYPE_PPM = 14;
+ /** File type is in XBM format. */
+ public static final int MAP_TYPE_XBM = 15;
+ /** File type is in XPM format. */
+ public static final int MAP_TYPE_XPM = 16;
+ /** File type is in ICO format. */
+ public static final int MAP_TYPE_ICO = 17;
+
+ // General LCI and LCR state
+ private final boolean mIsValid;
+
+ /*
+ These members are not final because we are not sure if the corresponding subelement will be
+ present until after the parsing process. However, the default value should be set as listed.
+ */
+ private boolean mIsLciValid = false;
+ private boolean mIsZValid = false;
+ private boolean mIsUsageValid = true; // By default this is assumed true
+ private boolean mIsBssidListValid = false;
+ private boolean mIsLocationCivicValid = false;
+ private boolean mIsMapImageValid = false;
+
+ // LCI Subelement LCI state
+ private double mLatitudeUncertainty;
+ private double mLatitude;
+ private double mLongitudeUncertainty;
+ private double mLongitude;
+ private int mAltitudeType;
+ private double mAltitudeUncertainty;
+ private double mAltitude;
+ private int mDatum;
+ private int mLciFlags;
+
+ // LCI Subelement Z state
+ private int mExpectedToMove;
+ private double mStaFloorNumber;
+ private double mStaHeightAboveFloorMeters;
+ private double mStaHeightAboveFloorUncertaintyMeters;
+
+ // LCI Subelement Usage Rights state
+ private boolean mUsageRetransmit;
+ private boolean mUsageRetentionExpires;
+ private boolean mUsageExtraInfoOnAssociation;
+
+ // LCI Subelement BSSID List state
+ private ArrayList<MacAddress> mBssidList;
+
+ // LCR Subelement Location Civic state
+ private String mCivicLocationCountryCode;
+ private String mCivicLocationString;
+ private CivicLocation mCivicLocation;
+
+ // LCR Subelement Map Image state
+ private int mMapImageType;
+ private URL mMapImageUrl;
+
+ /**
+ * Constructor
+ *
+ * @param lciBuffer the bytes received in the LCI Measurement Report Information Element
+ * @param lcrBuffer the bytes received in the LCR Measurement Report Information Element
+ *
+ * @hide
+ */
+ public ResponderLocation(byte[] lciBuffer, byte[] lcrBuffer) {
+ boolean isLciIeValid = false;
+ boolean isLcrIeValid = false;
+ setLciSubelementDefaults();
+ setZSubelementDefaults();
+ setUsageSubelementDefaults();
+ setBssidListSubelementDefaults();
+ setCivicLocationSubelementDefaults();
+ setMapImageSubelementDefaults();
+ if (lciBuffer != null && lciBuffer.length > LEAD_LCI_ELEMENT_BYTES.length) {
+ isLciIeValid = parseInformationElementBuffer(
+ MEASUREMENT_TYPE_LCI, lciBuffer, LEAD_LCI_ELEMENT_BYTES);
+ }
+ if (lcrBuffer != null && lcrBuffer.length > LEAD_LCR_ELEMENT_BYTES.length) {
+ isLcrIeValid = parseInformationElementBuffer(
+ MEASUREMENT_TYPE_LCR, lcrBuffer, LEAD_LCR_ELEMENT_BYTES);
+ }
+
+ boolean isLciValid = isLciIeValid && mIsUsageValid
+ && (mIsLciValid || mIsZValid || mIsBssidListValid);
+
+ boolean isLcrValid = isLcrIeValid && mIsUsageValid
+ && (mIsLocationCivicValid || mIsMapImageValid);
+
+ mIsValid = isLciValid || isLcrValid;
+
+ if (!mIsValid) {
+ setLciSubelementDefaults();
+ setZSubelementDefaults();
+ setCivicLocationSubelementDefaults();
+ setMapImageSubelementDefaults();
+ }
+ }
+
+ private ResponderLocation(Parcel in) {
+ // Object Validation
+ mIsValid = in.readByte() != 0;
+ mIsLciValid = in.readByte() != 0;
+ mIsZValid = in.readByte() != 0;
+ mIsUsageValid = in.readByte() != 0;
+ mIsBssidListValid = in.readByte() != 0;
+ mIsLocationCivicValid = in.readByte() != 0;
+ mIsMapImageValid = in.readByte() != 0;
+
+ // LCI Subelement LCI state
+ mLatitudeUncertainty = in.readDouble();
+ mLatitude = in.readDouble();
+ mLongitudeUncertainty = in.readDouble();
+ mLongitude = in.readDouble();
+ mAltitudeType = in.readInt();
+ mAltitudeUncertainty = in.readDouble();
+ mAltitude = in.readDouble();
+ mDatum = in.readInt();
+ mLciFlags = in.readInt();
+
+ // LCI Subelement Z state
+ mExpectedToMove = in.readInt();
+ mStaFloorNumber = in.readDouble();
+ mStaHeightAboveFloorMeters = in.readDouble();
+ mStaHeightAboveFloorUncertaintyMeters = in.readDouble();
+
+ // LCI Usage Rights
+ mUsageRetransmit = in.readByte() != 0;
+ mUsageRetentionExpires = in.readByte() != 0;
+ mUsageExtraInfoOnAssociation = in.readByte() != 0;
+
+ // LCI Subelement BSSID List
+ mBssidList = in.readArrayList(MacAddress.class.getClassLoader());
+
+ // LCR Subelement Location Civic
+ mCivicLocationCountryCode = in.readString();
+ mCivicLocationString = in.readString();
+ mCivicLocation = in.readParcelable(this.getClass().getClassLoader());
+
+ // LCR Subelement Map Image
+ mMapImageType = in.readInt();
+ try {
+ mMapImageUrl = new URL(in.readString());
+ } catch (MalformedURLException e) {
+ mMapImageUrl = null;
+ }
+ }
+
+ public static final Creator<ResponderLocation> CREATOR = new Creator<ResponderLocation>() {
+ @Override
+ public ResponderLocation createFromParcel(Parcel in) {
+ return new ResponderLocation(in);
+ }
+
+ @Override
+ public ResponderLocation[] newArray(int size) {
+ return new ResponderLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ // Object
+ parcel.writeByte((byte) (mIsValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsLciValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsZValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsUsageValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsBssidListValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsLocationCivicValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsMapImageValid ? 1 : 0));
+
+ // LCI Subelement LCI state
+ parcel.writeDouble(mLatitudeUncertainty);
+ parcel.writeDouble(mLatitude);
+ parcel.writeDouble(mLongitudeUncertainty);
+ parcel.writeDouble(mLongitude);
+ parcel.writeInt(mAltitudeType);
+ parcel.writeDouble(mAltitudeUncertainty);
+ parcel.writeDouble(mAltitude);
+ parcel.writeInt(mDatum);
+ parcel.writeInt(mLciFlags);
+
+ // LCI Subelement Z state
+ parcel.writeInt(mExpectedToMove);
+ parcel.writeDouble(mStaFloorNumber);
+ parcel.writeDouble(mStaHeightAboveFloorMeters);
+ parcel.writeDouble(mStaHeightAboveFloorUncertaintyMeters);
+
+ // LCI Usage Rights
+ parcel.writeByte((byte) (mUsageRetransmit ? 1 : 0));
+ parcel.writeByte((byte) (mUsageRetentionExpires ? 1 : 0));
+ parcel.writeByte((byte) (mUsageExtraInfoOnAssociation ? 1 : 0));
+
+ // LCI Subelement BSSID List
+ parcel.writeList(mBssidList);
+
+ // LCR Subelement Location Civic
+ parcel.writeString(mCivicLocationCountryCode);
+ parcel.writeString(mCivicLocationString);
+ parcel.writeParcelable(mCivicLocation, flags);
+
+ // LCR Subelement Map Image
+ parcel.writeInt(mMapImageType);
+ if (mMapImageUrl != null) {
+ parcel.writeString(mMapImageUrl.toString());
+ } else {
+ parcel.writeString("");
+ }
+ }
+
+ /**
+ * Test if the Information Element (IE) is in the correct format, and then parse its Subelements
+ * based on their type, and setting state in this object when present.
+ *
+ * @return a boolean indicating the success of the parsing function
+ */
+ private boolean parseInformationElementBuffer(
+ int ieType, byte[] buffer, byte[] expectedLeadBytes) {
+ int bufferPtr = 0;
+ int bufferLength = buffer.length;
+
+ // Ensure the buffer size is within expected limits
+ if (bufferLength < MIN_BUFFER_SIZE || bufferLength > MAX_BUFFER_SIZE) {
+ return false;
+ }
+
+ // Ensure the IE contains the correct leading bytes
+ byte[] leadBufferBytes = Arrays.copyOfRange(buffer, bufferPtr, expectedLeadBytes.length);
+ if (!Arrays.equals(leadBufferBytes, expectedLeadBytes)) {
+ return false;
+ }
+
+ // Iterate through the sub-elements contained in the Information Element (IE)
+ bufferPtr += expectedLeadBytes.length;
+ // Loop over the buffer ensuring there are 2-bytes available for each new subelement tested.
+ while (bufferPtr + 1 < bufferLength) {
+ byte subelement = buffer[bufferPtr++];
+ int subelementLength = buffer[bufferPtr++];
+ // Check there is enough data for the next subelement
+ if ((bufferPtr + subelementLength) > bufferLength || subelementLength <= 0) {
+ return false;
+ }
+
+ byte[] subelementData =
+ Arrays.copyOfRange(buffer, bufferPtr, bufferPtr + subelementLength);
+
+ if (ieType == MEASUREMENT_TYPE_LCI) {
+ switch (subelement) {
+ case SUBELEMENT_LCI:
+ mIsLciValid = parseSubelementLci(subelementData);
+ if (!mIsLciValid || (mLciFlags & LCI_FLAGS_MASK_VERSION) != LCI_VERSION_1) {
+ setLciSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_Z:
+ mIsZValid = parseSubelementZ(subelementData);
+ if (!mIsZValid) {
+ setZSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_USAGE:
+ mIsUsageValid = parseSubelementUsage(subelementData);
+ // Note: if the Usage Subelement is not valid, don't reset the state, as
+ // it is now indicating the whole ResponderLocation is invalid.
+ break;
+ case SUBELEMENT_BSSID_LIST:
+ mIsBssidListValid = parseSubelementBssidList(subelementData);
+ if (!mIsBssidListValid) {
+ setBssidListSubelementDefaults();
+ }
+ break;
+ default:
+ break; // skip over unused or vendor specific subelements
+ }
+ } else if (ieType == MEASUREMENT_TYPE_LCR) {
+ switch (subelement) {
+ case SUBELEMENT_LOCATION_CIVIC:
+ mIsLocationCivicValid = parseSubelementLocationCivic(subelementData);
+ if (!mIsLocationCivicValid) {
+ setCivicLocationSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_MAP_IMAGE:
+ mIsMapImageValid = parseSubelementMapImage(subelementData);
+ if (!mIsMapImageValid) {
+ setMapImageSubelementDefaults();
+ }
+ break;
+ default:
+ break; // skip over unused or other vendor specific subelements
+ }
+ }
+
+ bufferPtr += subelementLength;
+ }
+ return true;
+ }
+
+ /**
+ * Parse the LCI Sub-Element in the LCI Information Element (IE).
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementLci(byte[] buffer) {
+ if (buffer.length > SUBELEMENT_LCI_LENGTH) {
+ return false;
+ }
+ swapEndianByteByByte(buffer);
+ long[] subelementLciFields = getFieldData(buffer, SUBELEMENT_LCI_BIT_FIELD_LENGTHS);
+ if (subelementLciFields == null) {
+ return false;
+ }
+ // Set member state based on parsed buffer data
+ mLatitudeUncertainty = decodeLciLatLngUncertainty(
+ subelementLciFields[SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX]);
+ mLatitude = decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS,
+ SUBELEMENT_LCI_LAT_INDEX, LAT_ABS_LIMIT);
+ mLongitudeUncertainty = decodeLciLatLngUncertainty(
+ subelementLciFields[SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX]);
+ mLongitude =
+ decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS,
+ SUBELEMENT_LCI_LNG_INDEX, LNG_ABS_LIMIT);
+ mAltitudeType = (int) subelementLciFields[SUBELEMENT_LCI_ALT_TYPE_INDEX] & BYTE_MASK;
+ mAltitudeUncertainty =
+ decodeLciAltUncertainty(subelementLciFields[SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX]);
+ mAltitude =
+ Math.scalb(subelementLciFields[SUBELEMENT_LCI_ALT_INDEX], -ALTITUDE_FRACTION_BITS);
+ mDatum = (int) subelementLciFields[SUBELEMENT_LCI_DATUM_INDEX] & BYTE_MASK;
+ mLciFlags =
+ (int) subelementLciFields[SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX]
+ * LCI_FLAGS_MASK_REGLOC_AGREEMENT
+ | (int) subelementLciFields[SUBELEMENT_LCI_REGLOC_DSE_INDEX]
+ * LCI_FLAGS_MASK_REGLOC_DSE
+ | (int) subelementLciFields[SUBELEMENT_LCI_DEPENDENT_STA_INDEX]
+ * LCI_FLAGS_MASK_DEPENDENT_STA
+ | (int) subelementLciFields[SUBELEMENT_LCI_VERSION_INDEX];
+ return true;
+ }
+
+ /**
+ * Decode the floating point value of an encoded lat or lng in the LCI subelement field.
+ *
+ * @param fields the array of field data represented as longs
+ * @param bitFieldSizes the lengths of each field
+ * @param offset the offset of the field being decoded
+ * @param limit the maximum absolute value (note: different for lat vs lng)
+ * @return the floating point value of the lat or lng
+ */
+ private double decodeLciLatLng(long[] fields, int[] bitFieldSizes, int offset, double limit) {
+ double angle;
+ if ((fields[offset] & (long) Math.pow(2, bitFieldSizes[offset] - 1)) != 0) {
+ // Negative 2's complement value
+ // Note: The Math.pow(...) method cannot return a NaN value because the bitFieldSize
+ // for Lat or Lng is limited to exactly 34 bits.
+ angle = Math.scalb(fields[offset] - Math.pow(2, bitFieldSizes[offset]),
+ -LATLNG_FRACTION_BITS);
+ } else {
+ // Positive 2's complement value
+ angle = Math.scalb(fields[offset], -LATLNG_FRACTION_BITS);
+ }
+ if (angle > limit) {
+ angle = limit;
+ } else if (angle < -limit) {
+ angle = -limit;
+ }
+ return angle;
+ }
+
+ /**
+ * Coverts an encoded Lat/Lng uncertainty into a number of degrees.
+ *
+ * @param encodedValue the encoded uncertainty
+ * @return the value in degrees
+ */
+ private double decodeLciLatLngUncertainty(long encodedValue) {
+ return Math.pow(2, LATLNG_UNCERTAINTY_BASE - encodedValue);
+ }
+
+ /**
+ * Converts an encoded Alt uncertainty into a number of degrees.
+ *
+ * @param encodedValue the encoded uncertainty
+ * @return the value in degrees
+ */
+ private double decodeLciAltUncertainty(long encodedValue) {
+ return Math.pow(2, ALTITUDE_UNCERTAINTY_BASE - encodedValue);
+ }
+
+ /**
+ * Parse the Z subelement of the LCI IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementZ(byte[] buffer) {
+ if (buffer.length != SUBELEMENT_Z_LENGTH) {
+ return false;
+ }
+ swapEndianByteByByte(buffer);
+ long[] subelementZFields = getFieldData(buffer, SUBELEMENT_Z_BIT_FIELD_LENGTHS);
+ if (subelementZFields == null) {
+ return false;
+ }
+
+ mExpectedToMove =
+ (int) subelementZFields[SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX] & BYTE_MASK;
+
+ mStaFloorNumber = decodeZUnsignedToSignedValue(subelementZFields,
+ SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_STA_FLOOR_NUMBER_INDEX,
+ Z_FLOOR_NUMBER_FRACTION_BITS);
+
+ mStaHeightAboveFloorMeters = decodeZUnsignedToSignedValue(subelementZFields,
+ SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_STA_HEIGHT_ABOVE_FLOOR_INDEX,
+ Z_FLOOR_HEIGHT_FRACTION_BITS);
+
+ long zHeightUncertainty =
+ subelementZFields[SUBELEMENT_Z_STA_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX];
+ if (zHeightUncertainty > 0 && zHeightUncertainty < Z_MAX_HEIGHT_UNCERTAINTY_FACTOR) {
+ mStaHeightAboveFloorUncertaintyMeters =
+ Math.pow(2, Z_FLOOR_HEIGHT_FRACTION_BITS - zHeightUncertainty - 1);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Decode a two's complement encoded value, to a signed double based on the field length.
+ *
+ * @param fieldValues the array of field values reprented as longs
+ * @param fieldLengths the array of field lengths
+ * @param index the index of the field being decoded
+ * @param fraction the number of fractional bits in the value
+ * @return the signed value represented as a double
+ */
+ private double decodeZUnsignedToSignedValue(long[] fieldValues, int[] fieldLengths, int index,
+ int fraction) {
+ int value = (int) fieldValues[index];
+ int maxPositiveValue = (int) Math.pow(2, fieldLengths[index] - 1) - 1;
+ if (value > maxPositiveValue) {
+ value -= Math.pow(2, fieldLengths[index]);
+ }
+ return Math.scalb(value, -fraction);
+ }
+
+ /**
+ * Parse Subelement Usage Rights
+ */
+ private boolean parseSubelementUsage(byte[] buffer) {
+ if (buffer.length != SUBELEMENT_USAGE_LENGTH1
+ && buffer.length != SUBELEMENT_USAGE_LENGTH3) {
+ return false;
+ }
+ mUsageRetransmit =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETRANSMIT) != 0;
+ mUsageRetentionExpires =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES)
+ != 0;
+ mUsageExtraInfoOnAssociation =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY)
+ != 0;
+ // Note: the Retransmit flag must be true, and RetentionExpires, false for the
+ // ResponderLocation object to be usable by public applications.
+ return (mUsageRetransmit && !mUsageRetentionExpires);
+ }
+
+ /**
+ * Parse the BSSID List Subelement of the LCI IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementBssidList(byte[] buffer) {
+ if (buffer.length < SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH) {
+ return false;
+ }
+ if ((buffer.length - 1) % BYTES_IN_A_BSSID != 0) {
+ return false;
+ }
+
+ int maxBssidIndicator = (int) buffer[SUBELEMENT_BSSID_MAX_INDICATOR_INDEX] & BYTE_MASK;
+ int bssidListLength = (buffer.length - 1) / BYTES_IN_A_BSSID;
+ // Check the max number of BSSIDs agrees with the list length.
+ if (maxBssidIndicator != bssidListLength) {
+ return false;
+ }
+
+ int bssidOffset = SUBELEMENT_BSSID_LIST_INDEX;
+ for (int i = 0; i < bssidListLength; i++) {
+ byte[] bssid = Arrays.copyOfRange(buffer, bssidOffset, bssidOffset + BYTES_IN_A_BSSID);
+ MacAddress macAddress = MacAddress.fromBytes(bssid);
+ mBssidList.add(macAddress);
+ bssidOffset += BYTES_IN_A_BSSID;
+ }
+ return true;
+ }
+
+ /**
+ * Parse the Location Civic subelement in the LCR IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementLocationCivic(byte[] buffer) {
+ if (buffer.length < SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH
+ || buffer.length > SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH) {
+ return false;
+ }
+ mCivicLocationCountryCode =
+ new String(Arrays.copyOfRange(buffer, CIVIC_COUNTRY_CODE_INDEX,
+ CIVIC_TLV_LIST_INDEX)).toUpperCase();
+ CivicLocation civicLocation =
+ new CivicLocation(
+ Arrays.copyOfRange(buffer, CIVIC_TLV_LIST_INDEX, buffer.length),
+ mCivicLocationCountryCode);
+ if (!civicLocation.isValid()) {
+ return false;
+ }
+ this.mCivicLocation = civicLocation;
+ mCivicLocationString = civicLocation.toString();
+ return true;
+ }
+
+ /**
+ * Parse the Map Image subelement in the LCR IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementMapImage(byte[] buffer) {
+ if (buffer.length > SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH) {
+ return false;
+ }
+ int mapImageType = buffer[SUBELEMENT_IMAGE_MAP_TYPE_INDEX];
+ if (mapImageType < MAP_TYPE_URL_DEFINED || mapImageType > MAP_TYPE_DWG) {
+ return false;
+ }
+ this.mMapImageType = mapImageType;
+ byte[] urlBytes = Arrays.copyOfRange(buffer, 1, buffer.length);
+ try {
+ mMapImageUrl = new URL(new String(urlBytes));
+ } catch (MalformedURLException e) {
+ mMapImageUrl = null;
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Converts a byte array containing fields of variable size, into an array of longs where the
+ * field boundaries are defined in a constant int array passed as an argument.
+ *
+ * @param buffer the byte array containing all the fields
+ * @param bitFieldSizes the int array defining the size of each field
+ */
+ private long[] getFieldData(byte[] buffer, int[] bitFieldSizes) {
+ int bufferLengthBits = buffer.length * Byte.SIZE;
+ int sumBitFieldSizes = 0;
+ for (int i : bitFieldSizes) {
+ if (i > Long.SIZE) {
+ return null;
+ }
+ sumBitFieldSizes += i;
+ }
+ if (bufferLengthBits != sumBitFieldSizes) {
+ return null;
+ }
+ long[] fieldData = new long[bitFieldSizes.length];
+ int bufferBitPos = 0;
+ for (int fieldIndex = 0; fieldIndex < bitFieldSizes.length; fieldIndex++) {
+ int bitFieldSize = bitFieldSizes[fieldIndex];
+ long field = 0;
+ for (int n = 0; n < bitFieldSize; n++) {
+ field |= ((long) getBitAtBitOffsetInByteArray(buffer, bufferBitPos + n) << n);
+ }
+ fieldData[fieldIndex] = field;
+ bufferBitPos += bitFieldSize;
+ }
+ return fieldData;
+ }
+
+ /**
+ * Retrieves state of a bit at the bit-offset in a byte array, where the offset represents the
+ * bytes in contiguous data with each value in big endian order.
+ *
+ * @param buffer the data buffer of bytes containing all the fields
+ * @param bufferBitOffset the bit offset into the entire buffer
+ * @return a zero or one value, representing the state of that bit.
+ */
+ private int getBitAtBitOffsetInByteArray(byte[] buffer, int bufferBitOffset) {
+ int bufferIndex = bufferBitOffset / Byte.SIZE; // The byte index that contains the bit
+ int bitOffsetInByte = bufferBitOffset % Byte.SIZE; // The bit offset within that byte
+ int result = (buffer[bufferIndex] & (MSB_IN_BYTE >> bitOffsetInByte)) == 0 ? 0 : 1;
+ return result;
+ }
+
+ /**
+ * Reverses the order of the bits in each byte of a byte array.
+ *
+ * @param buffer the array containing each byte that will be reversed
+ */
+ private void swapEndianByteByByte(byte[] buffer) {
+ for (int n = 0; n < buffer.length; n++) {
+ byte currentByte = buffer[n];
+ byte reversedByte = 0; // Cleared value
+ byte bitSelectorMask = LSB_IN_BYTE;
+ for (int i = 0; i < Byte.SIZE; i++) {
+ reversedByte = (byte) (reversedByte << 1);
+ if ((currentByte & bitSelectorMask) != 0) {
+ reversedByte = (byte) (reversedByte | LSB_IN_BYTE);
+ }
+ bitSelectorMask = (byte) (bitSelectorMask << 1);
+ }
+ buffer[n] = reversedByte;
+ }
+ }
+
+ /**
+ * Sets the LCI subelement fields to the default undefined values.
+ */
+ private void setLciSubelementDefaults() {
+ mIsLciValid = false;
+ mLatitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mLatitude = 0;
+ mLongitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mLongitude = 0;
+ mAltitudeType = ALTITUDE_UNDEFINED;
+ mAltitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mAltitude = 0;
+ mDatum = DATUM_UNDEFINED;
+ mLciFlags = 0;
+ }
+
+ /**
+ * Sets the Z subelement fields to the default values when undefined.
+ */
+ private void setZSubelementDefaults() {
+ mIsZValid = false;
+ mExpectedToMove = 0;
+ mStaFloorNumber = 0;
+ mStaHeightAboveFloorMeters = 0;
+ mStaHeightAboveFloorUncertaintyMeters = 0;
+ }
+
+ /**
+ * Sets the Usage Policy subelement fields to the default undefined values.
+ */
+ private void setUsageSubelementDefaults() {
+ mUsageRetransmit = true;
+ mUsageRetentionExpires = false;
+ mUsageExtraInfoOnAssociation = false;
+ }
+
+ /**
+ * Sets the BSSID List subelement fields to the default values when undefined.
+ */
+ private void setBssidListSubelementDefaults() {
+ mIsBssidListValid = false;
+ mBssidList = new ArrayList<>();
+ }
+
+ /**
+ * Sets the LCR Civic Location subelement field to the default undefined value.
+ *
+ * @hide
+ */
+ public void setCivicLocationSubelementDefaults() {
+ mIsLocationCivicValid = false;
+ mCivicLocationCountryCode = "";
+ mCivicLocationString = "";
+ mCivicLocation = null;
+ }
+
+ /**
+ * Sets the LCR Map Image subelement field to the default values when undefined.
+ */
+ private void setMapImageSubelementDefaults() {
+ mIsMapImageValid = false;
+ mMapImageType = MAP_TYPE_URL_DEFINED;
+ mMapImageUrl = null;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ResponderLocation other = (ResponderLocation) obj;
+ return mIsValid == other.mIsValid
+ && mIsLciValid == other.mIsLciValid
+ && mIsZValid == other.mIsZValid
+ && mIsUsageValid == other.mIsUsageValid
+ && mIsBssidListValid == other.mIsBssidListValid
+ && mIsLocationCivicValid == other.mIsLocationCivicValid
+ && mIsMapImageValid == other.mIsMapImageValid
+ && mLatitudeUncertainty == other.mLatitudeUncertainty
+ && mLatitude == other.mLatitude
+ && mLongitudeUncertainty == other.mLongitudeUncertainty
+ && mLongitude == other.mLongitude
+ && mAltitudeType == other.mAltitudeType
+ && mAltitudeUncertainty == other.mAltitudeUncertainty
+ && mAltitude == other.mAltitude
+ && mDatum == other.mDatum
+ && mLciFlags == other.mLciFlags
+ && mExpectedToMove == other.mExpectedToMove
+ && mStaFloorNumber == other.mStaFloorNumber
+ && mStaHeightAboveFloorMeters == other.mStaHeightAboveFloorMeters
+ && mStaHeightAboveFloorUncertaintyMeters
+ == other.mStaHeightAboveFloorUncertaintyMeters
+ && mUsageRetransmit == other.mUsageRetransmit
+ && mUsageRetentionExpires == other.mUsageRetentionExpires
+ && mUsageExtraInfoOnAssociation == other.mUsageExtraInfoOnAssociation
+ && mBssidList.equals(other.mBssidList)
+ && mCivicLocationCountryCode.equals(other.mCivicLocationCountryCode)
+ && mCivicLocationString.equals(other.mCivicLocationString)
+ && Objects.equals(mCivicLocation, other.mCivicLocation)
+ && mMapImageType == other.mMapImageType
+ && Objects.equals(mMapImageUrl, other.mMapImageUrl);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsValid, mIsLciValid, mIsZValid, mIsUsageValid, mIsBssidListValid,
+ mIsLocationCivicValid, mIsMapImageValid, mLatitudeUncertainty, mLatitude,
+ mLongitudeUncertainty, mLongitude, mAltitudeType, mAltitudeUncertainty, mAltitude,
+ mDatum, mLciFlags, mExpectedToMove, mStaFloorNumber, mStaHeightAboveFloorMeters,
+ mStaHeightAboveFloorUncertaintyMeters, mUsageRetransmit, mUsageRetentionExpires,
+ mUsageExtraInfoOnAssociation, mBssidList, mCivicLocationCountryCode,
+ mCivicLocationString, mCivicLocation, mMapImageType, mMapImageUrl);
+ }
+
+ /**
+ * @return true if the ResponderLocation object is valid and contains useful information
+ * relevant to the location of the Responder. If this is ever false, this object will not be
+ * available to developers, and have a null value.
+ *
+ * @hide
+ */
+ public boolean isValid() {
+ return mIsValid;
+ }
+
+ /**
+ * @return true if the LCI subelement (containing Latitude, Longitude and Altitude) is valid.
+ *
+ * <p> This method tells us if the responder has provided its Location Configuration
+ * Information (LCI) directly, and is useful when an external database of responder locations
+ * is not available</p>
+ *
+ * <p>If isLciSubelementValid() returns true, all the LCI values provided by the corresponding
+ * getter methods will have been set as described by the responder, or else if false, they
+ * should not be used and will throw an IllegalStateException.</p>
+ */
+ public boolean isLciSubelementValid() {
+ return mIsLciValid;
+ }
+
+ /**
+ * @return the latitude uncertainty in degrees.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * <p> An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getLatitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mLatitudeUncertainty;
+ }
+
+ /**
+ * @return the latitude in degrees
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getLatitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitude(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mLatitude;
+ }
+
+ /**
+ * @return the Longitude uncertainty in degrees.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * <p> An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getLongitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLongitudeUncertainty(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mLongitudeUncertainty;
+ }
+
+ /**
+ * @return the Longitude in degrees..
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getLongitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mLongitude;
+ }
+
+ /**
+ * @return the Altitude type.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ @AltitudeType
+ public int getAltitudeType() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mAltitudeType;
+ }
+
+ /**
+ * @return the Altitude uncertainty in meters.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * <p>An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getAltitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mAltitudeUncertainty;
+ }
+
+ /**
+ * @return the Altitude in units defined by the altitude type.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getAltitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getAltitude(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mAltitude;
+ }
+
+ /**
+ * @return the Datum used for the LCI positioning information.
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ @DatumType
+ public int getDatum() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getDatum(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mDatum;
+ }
+
+ /**
+ * @return the LCI sub-element flags (5-bits).
+ *
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * <p>Note: The flags/version can be extracted by bitwise ANDing this value with the
+ * corresponding LCI_FLAGS_MASK_* .</p>
+ */
+ public int getLciFlags() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLciFlags(): invoked on an invalid result: mIsLciValid = false)");
+ }
+ return mLciFlags;
+ }
+
+ /**
+ * @return if the Z subelement (containing mobility, Floor, Height above floor) is valid.
+ */
+ public boolean isZsubelementValid() {
+ return mIsZValid;
+ }
+
+ /**
+ * @return an integer representing the mobility of the responder.
+ *
+ * Only valid if {@link #isZsubelementValid()} returns true, or will throw an exception.
+ */
+ @ExpectedToMoveType
+ public int getExpectedToMove() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getExpectedToMove(): invoked on an invalid result: mIsZValid = false)");
+ }
+ return mExpectedToMove;
+ }
+
+ /**
+ * @return the Z sub element STA Floor Number.
+ *
+ * Only valid if {@link #isZsubelementValid()} returns true, or will throw an exception.
+ *
+ * <p>Note: this number can be positive or negative, with value increments of +/- 1/16 of a
+ * floor.</p>.
+ */
+ public double getStaFloorNumber() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getStaFloorNumber(): invoked on an invalid result: mIsZValid = false)");
+ }
+ return mStaFloorNumber;
+ }
+
+ /**
+ * @return the Z subelement STA Height above the floor in meters.
+ *
+ * Only valid if {@link #isZsubelementValid()} returns true, or will throw an exception.
+ *
+ * <p>This value can be positive or negative. </p>
+ */
+ public double getStaHeightAboveFloorMeters() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getStaHeightAboveFloorMeters(): invoked on an invalid result: mIsZValid = false)");
+ }
+ return mStaHeightAboveFloorMeters;
+ }
+
+ /**
+ * @return the Z subelement STA Height above the floor uncertainty in meters.
+ *
+ * Only valid if {@link #isZsubelementValid()} returns true, or will throw an exception.
+ *
+ * <p>An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getStaHeightAboveFloorUncertaintyMeters() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getStaHeightAboveFloorUncertaintyMeters():"
+ + "invoked on an invalid result: mIsZValid = false)");
+ }
+ return mStaHeightAboveFloorUncertaintyMeters;
+ }
+
+ /**
+ * @return true if the location information received from the responder can be
+ * retransmitted to another device, physically separate from the one that received it.
+ *
+ * @hide
+ */
+ public boolean getRetransmitPolicyIndication() {
+ return mUsageRetransmit;
+ }
+
+ /**
+ * @return true if location-data received should expire (and be deleted)
+ * by the time provided in the getRelativeExpirationTimeHours() method.
+ *
+ *
+ * @hide
+ */
+ public boolean getRetentionExpiresIndication() {
+ return mUsageRetentionExpires;
+ }
+
+ /**
+ * @return true if there is extra location info available on association.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getExtraInfoOnAssociationIndication() {
+ return mUsageExtraInfoOnAssociation;
+ }
+
+ /**
+ * @return the list of colocated BSSIDs at the responder.
+ *
+ * <p> Will return an empty list when there are no bssids listed.
+ */
+ public List<MacAddress> getColocatedBssids() {
+ return mBssidList;
+ }
+
+ /**
+ * @return the civic location represented as an {@link Address} object (best effort).
+ *
+ * <p> Will return a {@code null} when there is no Civic Location define defined.
+ *
+ * @hide
+ */
+ @Nullable
+ public Address toCivicLocationAddress() {
+ if (mCivicLocation != null && mCivicLocation.isValid()) {
+ return mCivicLocation.toAddress();
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * @return the civic location two upper-case ASCII character country code defined in ISO 3166.
+ *
+ * <p> Will return a {@code null} when there is no country code defined.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getCivicLocationCountryCode() {
+ return mCivicLocationCountryCode;
+ }
+
+ /**
+ * @return the value of the Civic Location String associated with a key.
+ *
+ * <p> Will return a {@code null} when there is no value associated with the key provided.
+ *
+ * @param key used to find a corresponding value in the Civic Location Tuple list
+ *
+ * @hide
+ */
+ @Nullable
+ public String getCivicLocationElementValue(@CivicLocationKeysType int key) {
+ return mCivicLocation.getCivicElementValue(key);
+ }
+
+ /**
+ * @return the Map Image file type, referred to by getMapImageUrl(), encoded as an integer.
+ */
+ @MapImageType
+ public int getMapImageType() {
+ return mMapImageType;
+ }
+
+ /**
+ * @return a Url referencing a map-file showing the local floor plan.
+ *
+ * <p> Will return a {@code null} when there is no URL defined.
+ */
+ @Nullable
+ public URL getMapImageUrl() {
+ return mMapImageUrl;
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/rtt/CivicLocationTest.java b/wifi/tests/src/android/net/wifi/rtt/CivicLocationTest.java
new file mode 100644
index 0000000..f746fb7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/rtt/CivicLocationTest.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2018 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 junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.location.Address;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link CivicLocation}.
+ */
+@RunWith(JUnit4.class)
+public class CivicLocationTest {
+ private static final String sUsCountryCode = "US";
+
+ private static final byte[] sEmptyBuffer = {};
+
+ private static final byte[] sTestCivicLocationBuffer = {
+ (byte) 17,
+ (byte) 3,
+ (byte) 'a',
+ (byte) 'b',
+ (byte) 'c',
+ (byte) 4,
+ (byte) 4,
+ (byte) 'd',
+ (byte) 'e',
+ (byte) 'f',
+ (byte) 'g',
+ (byte) 12,
+ (byte) 1,
+ (byte) 'h'
+ };
+
+ private static final byte[] sTestCivicLocationBufferWithAddress = {
+ (byte) CivicLocationKeys.HNO,
+ (byte) 2,
+ (byte) '1',
+ (byte) '5',
+ (byte) CivicLocationKeys.PRIMARY_ROAD_NAME,
+ (byte) 4,
+ (byte) 'A',
+ (byte) 'l',
+ (byte) 't',
+ (byte) 'o',
+ (byte) CivicLocationKeys.STREET_NAME_POST_MODIFIER,
+ (byte) 4,
+ (byte) 'R',
+ (byte) 'o',
+ (byte) 'a',
+ (byte) 'd',
+ (byte) CivicLocationKeys.CITY,
+ (byte) 8,
+ (byte) 'M',
+ (byte) 't',
+ (byte) 'n',
+ (byte) ' ',
+ (byte) 'V',
+ (byte) 'i',
+ (byte) 'e',
+ (byte) 'w',
+ (byte) CivicLocationKeys.STATE,
+ (byte) 2,
+ (byte) 'C',
+ (byte) 'A',
+ (byte) CivicLocationKeys.POSTAL_CODE,
+ (byte) 5,
+ (byte) '9',
+ (byte) '4',
+ (byte) '0',
+ (byte) '4',
+ (byte) '3'
+ };
+
+ /**
+ * Test inValid for null CountryCode.
+ */
+ @Test
+ public void testCivicLocationNullCountryCode() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, null);
+
+ boolean valid = civicLocation.isValid();
+
+ assertFalse(valid);
+ }
+
+ /**
+ * Test inValid for CountryCode too short.
+ */
+ @Test
+ public void testCivicLocationCountryCodeTooShort() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, "X");
+
+ boolean valid = civicLocation.isValid();
+
+ assertFalse(valid);
+ }
+
+ /**
+ * Test inValid for CountryCode too long.
+ */
+ @Test
+ public void testCivicLocationCountryCodeTooLong() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, "XYZ");
+
+ boolean valid = civicLocation.isValid();
+
+ assertFalse(valid);
+ }
+
+ /**
+ * Test inValid for null CivicLocation Buffer
+ */
+ @Test
+ public void testCivicLocationNullBuffer() {
+ CivicLocation civicLocation = new CivicLocation(null, sUsCountryCode);
+
+ boolean valid = civicLocation.isValid();
+
+ assertFalse(valid);
+ }
+
+ /**
+ * Test inValid for Empty CivicLocation Buffer.
+ */
+ @Test
+ public void testCivicLocationEmptyBuffer() {
+ CivicLocation civicLocation = new CivicLocation(sEmptyBuffer, sUsCountryCode);
+
+ boolean valid = civicLocation.isValid();
+
+ assertFalse(valid);
+ }
+
+ /**
+ * Test for valid CivicLocationBuffer and Country Code.
+ */
+ @Test
+ public void testCivicLocationValid() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, sUsCountryCode);
+
+ boolean valid = civicLocation.isValid();
+
+ assertTrue(valid);
+ }
+
+ /**
+ * Test toString Representation
+ */
+ @Test
+ public void testCivicLocationToString() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, sUsCountryCode);
+
+ String str = civicLocation.toString();
+
+ assertEquals("{4=defg, 12=h, 17=abc}", str);
+ }
+
+ /**
+ * Test the toString
+ */
+ @Test
+ public void testCivicLocationgetElementValue() {
+ CivicLocation civicLocation = new CivicLocation(sTestCivicLocationBuffer, sUsCountryCode);
+
+ String value1 = civicLocation.getCivicElementValue(4);
+ String value2 = civicLocation.getCivicElementValue(17);
+ String value3 = civicLocation.getCivicElementValue(12);
+ String value4 = civicLocation.getCivicElementValue(156); // not in test data
+ String value5 = civicLocation.getCivicElementValue(276); // greater than key index
+
+ assertEquals("defg", value1);
+ assertEquals("abc", value2);
+ assertEquals("h", value3);
+ assertNull(value4);
+ assertNull(value5);
+ }
+
+ /* Test toAddress representation */
+ @Test
+ public void testCivicLocationToAddress() {
+ CivicLocation civicLocation =
+ new CivicLocation(sTestCivicLocationBufferWithAddress, sUsCountryCode);
+
+ Address address = civicLocation.toAddress();
+
+ assertEquals("", address.getAddressLine(0));
+ assertEquals("15 Alto", address.getAddressLine(1));
+ assertEquals("Mtn View", address.getAddressLine(2));
+ assertEquals("CA 94043", address.getAddressLine(3));
+ assertEquals("US", address.getAddressLine(4));
+ }
+
+ /**
+ * Test toString Representation
+ */
+ @Test
+ public void testCivicLocationToString2() {
+ CivicLocation civicLocation =
+ new CivicLocation(sTestCivicLocationBufferWithAddress, sUsCountryCode);
+
+ String str = civicLocation.toString();
+
+ assertEquals("{1=CA, 3=Mtn View, 19=15, 24=94043, 34=Alto, 39=Road}", str);
+ }
+
+ /** Test object is Parcellable */
+ @Test
+ public void testCivicLocationParcelable() {
+ CivicLocation civicLocation =
+ new CivicLocation(sTestCivicLocationBufferWithAddress, sUsCountryCode);
+
+ Parcel parcel = Parcel.obtain();
+ civicLocation.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ CivicLocation civicLocationFromParcel =
+ CivicLocation.CREATOR.createFromParcel(parcel);
+
+ assertEquals(civicLocationFromParcel, civicLocation);
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
new file mode 100644
index 0000000..9efb642
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/rtt/ResponderLocationTest.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2018 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.location.Address;
+import android.net.MacAddress;
+import android.os.Parcel;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+/**
+ * Tests for {@link ResponderLocation}.
+ */
+@RunWith(JUnit4.class)
+public class ResponderLocationTest {
+ private static final double LATLNG_TOLERANCE_DEGREES = 0.001;
+ private static final double ALT_TOLERANCE_METERS = 0.01;
+ private static final double HEIGHT_TOLERANCE_METERS = 0.01;
+ private static final int INDEX_ELEMENT_TYPE = 2;
+ private static final int INDEX_SUBELEMENT_TYPE = 0;
+ private static final int INDEX_SUBELEMENT_LENGTH = 1;
+
+ /* Test Buffers */
+
+ private static final byte[] sTestLciIeHeader = {
+ (byte) 0x01, (byte) 0x00, (byte) 0x08 // LCI Information Element (IE)
+ };
+
+ private static final byte[] sTestLciShortBuffer = {
+ (byte) 0x00
+ };
+
+ private static final byte[] sTestLciSE = {
+ (byte) 0x00, // Subelement LCI
+ (byte) 16, // Subelement LCI length always = 16
+ (byte) 0x52,
+ (byte) 0x83,
+ (byte) 0x4d,
+ (byte) 0x12,
+ (byte) 0xef,
+ (byte) 0xd2,
+ (byte) 0xb0,
+ (byte) 0x8b,
+ (byte) 0x9b,
+ (byte) 0x4b,
+ (byte) 0xf1,
+ (byte) 0xcc,
+ (byte) 0x2c,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x41
+ };
+
+ private static final byte[] sTestZHeightSE = {
+ (byte) 0x04, // Subelement Z
+ (byte) 6, // Length always 6
+ (byte) 0x00, // LSB STA Floor Info (2 bytes)
+ (byte) 0x01, // MSB
+ (byte) 0xcd, // LSB Height(m) (3 bytes)
+ (byte) 0x2c,
+ (byte) 0x00, // MSB Height(m)
+ (byte) 0x0e, // STA Height Uncertainty
+ };
+
+ private static final byte[] sTestUsageSE1 = {
+ (byte) 0x06, // Subelement Usage Rights
+ (byte) 1, // Length 1 (with no retention limit)
+ (byte) 0x01, // Retransmit ok, No expiration, no extra info available
+ };
+
+ private static final byte[] sTestUsageSE2 = {
+ (byte) 0x06, // Subelement Usage Rights
+ (byte) 3, // Length 3 (including retention limit)
+ (byte) 0x06, // Retransmit not ok, Expiration, extra info available
+ (byte) 0x00, // LSB expiration time (0x8000 = 32768 hrs)
+ (byte) 0x80 // MSB expiration time
+ };
+
+ private static final byte[] sTestBssidListSE = {
+ (byte) 0x07, // Subelement BSSID list
+ (byte) 13, // length dependent on number of BSSIDs in list
+ (byte) 0x02, // Number of BSSIDs in list
+ (byte) 0x01, // BSSID #1 (MSB)
+ (byte) 0x02,
+ (byte) 0x03,
+ (byte) 0x04,
+ (byte) 0x05,
+ (byte) 0x06, // (LSB)
+ (byte) 0xf1, // BSSID #2 (MSB)
+ (byte) 0xf2,
+ (byte) 0xf3,
+ (byte) 0xf4,
+ (byte) 0xf5,
+ (byte) 0xf6 // (LSB)
+ };
+
+ private static final byte[] sTestLcrBufferHeader = {
+ (byte) 0x01, (byte) 0x00, (byte) 0x0b,
+ };
+
+ private static final byte[] sEmptyBuffer = {};
+
+ private static final byte[] sTestCivicLocationSEWithAddress = {
+ (byte) 0, // Civic Location Subelement
+ (byte) 39, // Length of subelement value
+ (byte) 'U', // CountryCodeChar1
+ (byte) 'S', // CountryCodeChar2
+ (byte) CivicLocationKeys.HNO,
+ (byte) 2,
+ (byte) '1',
+ (byte) '5',
+ (byte) CivicLocationKeys.PRIMARY_ROAD_NAME,
+ (byte) 4,
+ (byte) 'A',
+ (byte) 'l',
+ (byte) 't',
+ (byte) 'o',
+ (byte) CivicLocationKeys.STREET_NAME_POST_MODIFIER,
+ (byte) 4,
+ (byte) 'R',
+ (byte) 'o',
+ (byte) 'a',
+ (byte) 'd',
+ (byte) CivicLocationKeys.CITY,
+ (byte) 8,
+ (byte) 'M',
+ (byte) 't',
+ (byte) 'n',
+ (byte) ' ',
+ (byte) 'V',
+ (byte) 'i',
+ (byte) 'e',
+ (byte) 'w',
+ (byte) CivicLocationKeys.STATE,
+ (byte) 2,
+ (byte) 'C',
+ (byte) 'A',
+ (byte) CivicLocationKeys.POSTAL_CODE,
+ (byte) 5,
+ (byte) '9',
+ (byte) '4',
+ (byte) '0',
+ (byte) '4',
+ (byte) '3'
+ };
+
+ // Buffer representing: "https://map.com/mall.jpg"
+ private static final byte[] sTestMapUrlSE = {
+ (byte) 5, // Map URL Subelement
+ (byte) 25,
+ (byte) ResponderLocation.MAP_TYPE_URL_DEFINED,
+ (byte) 'h',
+ (byte) 't',
+ (byte) 't',
+ (byte) 'p',
+ (byte) 's',
+ (byte) ':',
+ (byte) '/',
+ (byte) '/',
+ (byte) 'm',
+ (byte) 'a',
+ (byte) 'p',
+ (byte) '.',
+ (byte) 'c',
+ (byte) 'o',
+ (byte) 'm',
+ (byte) '/',
+ (byte) 'm',
+ (byte) 'a',
+ (byte) 'l',
+ (byte) 'l',
+ (byte) '.',
+ (byte) 'j',
+ (byte) 'p',
+ (byte) 'g'
+ };
+
+ /**
+ * Test if the lci and lcr buffers are null.
+ */
+ @Test
+ public void testIfLciOrLcrIsNull() {
+ ResponderLocation responderLocation = new ResponderLocation(null, null);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test if the lci and lcr buffers are empty.
+ */
+ @Test
+ public void testIfLciOrLcrIsEmpty() {
+ ResponderLocation responderLocation = new ResponderLocation(sEmptyBuffer, sEmptyBuffer);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test if the lci subelement only has one byte
+ */
+ @Test
+ public void testIfLciShortBuffer() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciShortBuffer);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test that the example buffer contains a valid LCI Subelement.
+ */
+ @Test
+ public void testLciValidSubelement() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertTrue(valid);
+ assertTrue(lciValid);
+ assertFalse(zValid);
+ assertEquals(0.0009765625, responderLocation.getLatitudeUncertainty());
+ assertEquals(-33.857009, responderLocation.getLatitude(),
+ LATLNG_TOLERANCE_DEGREES);
+ assertEquals(0.0009765625, responderLocation.getLongitudeUncertainty());
+ assertEquals(151.215200, responderLocation.getLongitude(),
+ LATLNG_TOLERANCE_DEGREES);
+ assertEquals(1, responderLocation.getAltitudeType());
+ assertEquals(64.0, responderLocation.getAltitudeUncertainty());
+ assertEquals(11.2, responderLocation.getAltitude(), ALT_TOLERANCE_METERS);
+ assertEquals(1, responderLocation.getDatum()); // WGS84
+ int lciFlags = responderLocation.getLciFlags();
+ assertEquals(0, lciFlags & ResponderLocation.LCI_FLAGS_MASK_REGLOC_AGREEMENT);
+ assertEquals(0, lciFlags & ResponderLocation.LCI_FLAGS_MASK_REGLOC_DSE);
+ assertEquals(0, lciFlags & ResponderLocation.LCI_FLAGS_MASK_DEPENDENT_STA);
+ assertEquals(1, lciFlags & ResponderLocation.LCI_FLAGS_MASK_VERSION);
+ }
+
+ /**
+ * Test for an invalid LCI element.
+ */
+ @Test
+ public void testLciInvalidElement() {
+ byte[] testBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ testBuffer[INDEX_ELEMENT_TYPE] = (byte) 0xFF;
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test for an invalid subelement type.
+ */
+ @Test
+ public void testSkipLciSubElementUnusedOrUnknown() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ // Corrupt the subelement type to an unknown type.
+ testLciBuffer[sTestLciIeHeader.length + INDEX_SUBELEMENT_TYPE] = (byte) 0x77;
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test for a subelement LCI length too small.
+ */
+ @Test
+ public void testInvalidLciSubElementLengthTooSmall() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ // Corrupt the length making it too small.
+ testLciBuffer[sTestLciIeHeader.length + INDEX_SUBELEMENT_LENGTH] = (byte) 0x01;
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test for a subelement LCI length too big.
+ */
+ @Test
+ public void testInvalidLciSubElementLengthTooBig() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ // Corrupt the length making it too big.
+ testLciBuffer[sTestLciIeHeader.length + INDEX_SUBELEMENT_TYPE] = (byte) 0x11;
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean lciValid = responderLocation.isLciSubelementValid();
+ boolean zValid = responderLocation.isZsubelementValid();
+
+ assertFalse(valid);
+ assertFalse(lciValid);
+ assertFalse(zValid);
+ }
+
+ /**
+ * Test for a valid Z (Height) subelement following an LCI subelement.
+ */
+ @Test
+ public void testLciValidZBufferSEAfterLci() {
+ byte[] testBufferTmp = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testBuffer = concatenateArrays(testBufferTmp, sTestZHeightSE);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean isValid = responderLocation.isValid();
+ boolean isZValid = responderLocation.isZsubelementValid();
+ boolean isLciValid = responderLocation.isLciSubelementValid();
+ double staFloorNumber = responderLocation.getStaFloorNumber();
+ double staHeightAboveFloorMeters = responderLocation.getStaHeightAboveFloorMeters();
+ double staHeightAboveFloorUncertaintyMeters =
+ responderLocation.getStaHeightAboveFloorUncertaintyMeters();
+
+ assertTrue(isValid);
+ assertTrue(isZValid);
+ assertTrue(isLciValid);
+ assertEquals(4.0, staFloorNumber);
+ assertEquals(2.8, staHeightAboveFloorMeters, HEIGHT_TOLERANCE_METERS);
+ assertEquals(0.125, staHeightAboveFloorUncertaintyMeters);
+ }
+
+ /**
+ * Test for a valid Usage Policy that is unrestrictive
+ */
+ @Test
+ public void testLciOpenUsagePolicy() {
+ byte[] testBufferTmp = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testBuffer = concatenateArrays(testBufferTmp, sTestUsageSE1);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean retransmit = responderLocation.getRetransmitPolicyIndication();
+ boolean expiration = responderLocation.getRetentionExpiresIndication();
+ boolean extraInfo = responderLocation.getExtraInfoOnAssociationIndication();
+
+ assertTrue(valid);
+ assertTrue(retransmit);
+ assertFalse(expiration);
+ assertFalse(extraInfo);
+ }
+
+ /**
+ * Test for a valid Usage Policy that is restrictive
+ */
+ @Test
+ public void testLciRestrictiveUsagePolicy() {
+ byte[] testBufferTmp = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testBuffer = concatenateArrays(testBufferTmp, sTestUsageSE2);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ boolean retransmit = responderLocation.getRetransmitPolicyIndication();
+ boolean expiration = responderLocation.getRetentionExpiresIndication();
+ boolean extraInfo = responderLocation.getExtraInfoOnAssociationIndication();
+
+ assertFalse(valid);
+ assertFalse(retransmit);
+ assertTrue(expiration);
+ assertTrue(extraInfo);
+ }
+
+ /**
+ * Test for a valid BSSID element following an LCI subelement.
+ */
+ @Test
+ public void testLciBssidListSEAfterLci() {
+ byte[] testBufferTmp = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testBuffer = concatenateArrays(testBufferTmp, sTestBssidListSE);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ List<MacAddress> bssidList = responderLocation.getColocatedBssids();
+
+ assertTrue(valid);
+ assertEquals(2, bssidList.size());
+ MacAddress macAddress1 = bssidList.get(0);
+ assertEquals("01:02:03:04:05:06", macAddress1.toString());
+ MacAddress macAddress2 = bssidList.get(1);
+ assertEquals("f1:f2:f3:f4:f5:f6", macAddress2.toString());
+ }
+
+ /**
+ * Test for a valid BSSID element before and LCI element
+ */
+ @Test
+ public void testLciBssidListSEBeforeLci() {
+ byte[] testBufferTmp = concatenateArrays(sTestLciIeHeader, sTestBssidListSE);
+ byte[] testBuffer = concatenateArrays(testBufferTmp, sTestLciSE);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testBuffer, sTestLcrBufferHeader);
+
+ boolean valid = responderLocation.isValid();
+ List<MacAddress> bssidList = responderLocation.getColocatedBssids();
+
+ assertTrue(valid);
+ assertEquals(2, bssidList.size());
+ MacAddress macAddress1 = bssidList.get(0);
+ assertEquals("01:02:03:04:05:06", macAddress1.toString());
+ MacAddress macAddress2 = bssidList.get(1);
+ assertEquals("f1:f2:f3:f4:f5:f6", macAddress2.toString());
+ }
+
+ /**
+ * Test that a valid address can be extracted from a valid lcr buffer with Civic Location.
+ */
+ @Test
+ public void testLcrTestCivicLocationAddress() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testLcrBuffer =
+ concatenateArrays(sTestLcrBufferHeader, sTestCivicLocationSEWithAddress);
+ ResponderLocation responderLocation = new ResponderLocation(testLciBuffer, testLcrBuffer);
+
+ boolean valid = responderLocation.isValid();
+ String countryCode = responderLocation.getCivicLocationCountryCode();
+ Address address = responderLocation.toCivicLocationAddress();
+
+ assertTrue(valid);
+ assertEquals("US", countryCode);
+ assertEquals("", address.getAddressLine(0));
+ assertEquals("15 Alto", address.getAddressLine(1));
+ assertEquals("Mtn View", address.getAddressLine(2));
+ assertEquals("CA 94043", address.getAddressLine(3));
+ assertEquals("US", address.getAddressLine(4));
+ }
+
+ /**
+ * Test that a URL can be extracted from a valid lcr buffer with a map image subelement.
+ */
+ @Test
+ public void testLcrCheckMapUrlIsValid() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ byte[] testLcrBuffer = concatenateArrays(sTestLcrBufferHeader, sTestMapUrlSE);
+ ResponderLocation responderLocation = new ResponderLocation(testLciBuffer, testLcrBuffer);
+
+ boolean valid = responderLocation.isValid();
+ int mapImageType = responderLocation.getMapImageType();
+ String urlString = "";
+ if (responderLocation.getMapImageUrl() != null) {
+ urlString = responderLocation.getMapImageUrl().toString();
+ }
+
+ assertTrue(valid);
+ assertEquals(ResponderLocation.MAP_TYPE_URL_DEFINED, mapImageType);
+ assertEquals("https://map.com/mall.jpg", urlString);
+ }
+
+ /**
+ * Test the object is parcelable
+ */
+ @Test
+ public void testResponderLocationParcelable() {
+ byte[] testLciBuffer = concatenateArrays(sTestLciIeHeader, sTestLciSE);
+ ResponderLocation responderLocation =
+ new ResponderLocation(testLciBuffer, sTestLcrBufferHeader);
+
+ Parcel parcel = Parcel.obtain();
+ responderLocation.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ResponderLocation responderLocationFromParcel =
+ ResponderLocation.CREATOR.createFromParcel(parcel);
+
+ assertEquals(responderLocationFromParcel, responderLocation);
+ }
+
+ /* Helper Method */
+
+ /**
+ * Concatenate two arrays.
+ *
+ * @param a first array
+ * @param b second array
+ * @return a third array which is the concatenation of the two array params
+ */
+ private byte[] concatenateArrays(byte[] a, byte[] b) {
+ int aLen = a.length;
+ int bLen = b.length;
+ byte[] c = new byte[aLen + bLen];
+ System.arraycopy(a, 0, c, 0, aLen);
+ System.arraycopy(b, 0, c, aLen, bLen);
+ return c;
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index afc7dff..53bd837 100644
--- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -81,7 +81,7 @@
List<RangingResult> results = new ArrayList<>();
results.add(
new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5,
- 10, 8, 5, null, null, 666));
+ 10, 8, 5, null, null, null, 666));
RangingResultCallback callbackMock = mock(RangingResultCallback.class);
ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
@@ -243,7 +243,7 @@
// RangingResults constructed with a MAC address
RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
Parcel parcelW = Parcel.obtain();
result.writeToParcel(parcelW, 0);
@@ -259,7 +259,7 @@
// RangingResults constructed with a PeerHandle
result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, null, null, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp);
parcelW = Parcel.obtain();
result.writeToParcel(parcelW, 0);
@@ -292,9 +292,9 @@
byte[] lcr = { };
RangingResult rr1 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
RangingResult rr2 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
- numAttemptedMeasurements, numSuccessfulMeasurements, null, null, timestamp);
+ numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp);
assertEquals(rr1, rr2);
}