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);
     }