Merge "Improve handling of devices without a WebView."
diff --git a/Android.mk b/Android.mk
index aa7caa5..7d31fdc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -552,6 +552,8 @@
 	wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \
 	wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
+	wifi/java/android/net/wifi/rtt/IRttCallback.aidl \
+	wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
 	wifi/java/android/net/wifi/IRttManager.aidl \
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
@@ -721,6 +723,8 @@
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \
+	frameworks/base/wifi/java/android/net/wifi/rtt/RangingRequest.aidl \
+	frameworks/base/wifi/java/android/net/wifi/rtt/RangingResult.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \
 	frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \
diff --git a/api/current.txt b/api/current.txt
index 25cac5c..6fae882 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40765,6 +40765,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -52394,6 +52395,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/system-current.txt b/api/system-current.txt
index 82cdbaf..8c1169a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -44371,6 +44371,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -56457,6 +56458,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/test-current.txt b/api/test-current.txt
index 34b663be..65918c5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -41115,6 +41115,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -52965,6 +52966,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 7e78d6c..b16188e 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -31,12 +31,13 @@
 
 LOCAL_SRC_FILES += \
     iot/iotbootanimation_main.cpp \
-    iot/BootAction.cpp
+    iot/BootAction.cpp \
+    iot/BootParameters.cpp \
 
 LOCAL_SHARED_LIBRARIES += \
     libandroidthings \
     libbase \
-    libbinder
+    libbinder \
 
 LOCAL_STATIC_LIBRARIES += cpufeatures
 
diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp
index 665b4d9..fa79744 100644
--- a/cmds/bootanimation/iot/BootAction.cpp
+++ b/cmds/bootanimation/iot/BootAction.cpp
@@ -19,96 +19,20 @@
 #define LOG_TAG "BootAction"
 
 #include <dlfcn.h>
-#include <fcntl.h>
 
-#include <map>
-
-#include <android-base/file.h>
-#include <android-base/strings.h>
-#include <base/json/json_parser.h>
-#include <base/json/json_value_converter.h>
-#include <cpu-features.h>
 #include <pio/peripheral_manager_client.h>
 #include <utils/Log.h>
 
-using android::base::ReadFileToString;
-using android::base::RemoveFileIfExists;
-using android::base::Split;
-using android::base::Join;
-using android::base::StartsWith;
-using android::base::EndsWith;
-using base::JSONReader;
-using base::Value;
-
 namespace android {
 
-// Brightness and volume are stored as integer strings in next_boot.json.
-// They are divided by this constant to produce the actual float values in
-// range [0.0, 1.0]. This constant must match its counterpart in
-// DeviceManager.
-constexpr const float kFloatScaleFactor = 1000.0f;
-
-constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
-constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
-
-bool loadParameters(BootAction::SavedBootParameters* parameters)
-{
-    std::string contents;
-    if (!ReadFileToString(kLastBootFile, &contents)) {
-        if (errno != ENOENT)
-            ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
-
-        return false;
-    }
-
-    std::unique_ptr<Value> json = JSONReader::Read(contents);
-    if (json.get() == nullptr) return false;
-
-    JSONValueConverter<BootAction::SavedBootParameters> converter;
-    if (!converter.Convert(*(json.get()), parameters)) return false;
-
-    return true;
-}
-
-void BootAction::SavedBootParameters::RegisterJSONConverter(
-        JSONValueConverter<SavedBootParameters> *converter) {
-    converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
-    converter->RegisterIntField("volume", &SavedBootParameters::volume);
-    converter->RegisterRepeatedString("param_names",
-                                      &SavedBootParameters::param_names);
-    converter->RegisterRepeatedString("param_values",
-                                      &SavedBootParameters::param_values);
-}
-
 BootAction::~BootAction() {
     if (mLibHandle != nullptr) {
         dlclose(mLibHandle);
     }
 }
 
-void BootAction::swapBootConfigs() {
-    // rename() will fail if next_boot.json doesn't exist, so delete
-    // last_boot.json manually first.
-    std::string err;
-    if (!RemoveFileIfExists(kLastBootFile, &err))
-        ALOGE("Unable to delete last boot file: %s", err.c_str());
-
-    if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
-        ALOGE("Unable to swap boot files: %s", strerror(errno));
-
-    int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
-    if (fd == -1) {
-        ALOGE("Unable to create next boot file: %s", strerror(errno));
-    } else {
-        // Make next_boot.json writible to everyone so DeviceManagementService
-        // can save parameters there.
-        if (fchmod(fd, DEFFILEMODE))
-            ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
-        close(fd);
-    }
-}
-
-bool BootAction::init(const std::string& libraryPath) {
+bool BootAction::init(const std::string& libraryPath,
+                      const std::vector<ABootActionParameter>& parameters) {
     APeripheralManagerClient* client = nullptr;
     ALOGD("Connecting to peripheralmanager");
     // Wait for peripheral manager to come up.
@@ -122,27 +46,6 @@
     ALOGD("Peripheralmanager is up.");
     APeripheralManagerClient_delete(client);
 
-    float brightness = -1.0f;
-    float volume = -1.0f;
-    std::vector<ABootActionParameter> parameters;
-    SavedBootParameters saved_parameters;
-
-    if (loadParameters(&saved_parameters)) {
-        // TODO(b/65462981): Do something with brightness and volume?
-        brightness = saved_parameters.brightness / kFloatScaleFactor;
-        volume = saved_parameters.volume / kFloatScaleFactor;
-
-        if (saved_parameters.param_names.size() == saved_parameters.param_values.size()) {
-            for (size_t i = 0; i < saved_parameters.param_names.size(); i++) {
-                parameters.push_back({
-                        .key = saved_parameters.param_names[i]->c_str(),
-                        .value = saved_parameters.param_values[i]->c_str()
-                });
-            }
-        } else {
-            ALOGW("Parameter names and values size mismatch");
-        }
-    }
 
     ALOGI("Loading boot action %s", libraryPath.c_str());
     mLibHandle = dlopen(libraryPath.c_str(), RTLD_NOW);
diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h
index 3cd43be..5e2495f 100644
--- a/cmds/bootanimation/iot/BootAction.h
+++ b/cmds/bootanimation/iot/BootAction.h
@@ -17,38 +17,21 @@
 #ifndef _BOOTANIMATION_BOOTACTION_H
 #define _BOOTANIMATION_BOOTACTION_H
 
-#include <map>
 #include <string>
+#include <vector>
 
-#include <base/json/json_value_converter.h>
 #include <boot_action/boot_action.h>  // libandroidthings native API.
 #include <utils/RefBase.h>
 
-using base::JSONValueConverter;
-
 namespace android {
 
 class BootAction : public RefBase {
 public:
-    struct SavedBootParameters {
-      int brightness;
-      int volume;
-      ScopedVector<std::string> param_names;
-      ScopedVector<std::string> param_values;
-      static void RegisterJSONConverter(
-          JSONValueConverter<SavedBootParameters>* converter);
-    };
-
     ~BootAction();
 
-    // Rename next_boot.json to last_boot.json so that we don't repeat
-    // parameters if there is a crash before the framework comes up.
-    // TODO(b/65462981): Is this what we want to do? Should we swap in the
-    // framework instead?
-    static void swapBootConfigs();
-
     // libraryPath is a fully qualified path to the target .so library.
-    bool init(const std::string& libraryPath);
+    bool init(const std::string& libraryPath,
+              const std::vector<ABootActionParameter>& parameters);
 
     // The animation is going to start playing partNumber for the playCount'th
     // time, update the action as needed.
diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp
new file mode 100644
index 0000000..da6ad0d
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BootParameters.h"
+
+#define LOG_TAG "BootParameters"
+
+#include <fcntl.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <base/json/json_parser.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_value_converter.h>
+#include <utils/Log.h>
+
+using android::base::RemoveFileIfExists;
+using android::base::ReadFileToString;
+using base::JSONReader;
+using base::JSONValueConverter;
+using base::Value;
+
+namespace android {
+
+namespace {
+
+// Brightness and volume are stored as integer strings in next_boot.json.
+// They are divided by this constant to produce the actual float values in
+// range [0.0, 1.0]. This constant must match its counterpart in
+// DeviceManager.
+constexpr const float kFloatScaleFactor = 1000.0f;
+
+constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
+constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
+
+void swapBootConfigs() {
+    // rename() will fail if next_boot.json doesn't exist, so delete
+    // last_boot.json manually first.
+    std::string err;
+    if (!RemoveFileIfExists(kLastBootFile, &err))
+        ALOGE("Unable to delete last boot file: %s", err.c_str());
+
+    if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
+        ALOGE("Unable to swap boot files: %s", strerror(errno));
+
+    int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
+    if (fd == -1) {
+        ALOGE("Unable to create next boot file: %s", strerror(errno));
+    } else {
+        // Make next_boot.json writable to everyone so DeviceManagementService
+        // can save saved_parameters there.
+        if (fchmod(fd, DEFFILEMODE))
+            ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
+        close(fd);
+    }
+}
+
+}  // namespace
+
+BootParameters::SavedBootParameters::SavedBootParameters()
+    : brightness(-kFloatScaleFactor), volume(-kFloatScaleFactor) {}
+
+void BootParameters::SavedBootParameters::RegisterJSONConverter(
+        JSONValueConverter<SavedBootParameters>* converter) {
+    converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
+    converter->RegisterIntField("volume", &SavedBootParameters::volume);
+    converter->RegisterRepeatedString("param_names",
+                                      &SavedBootParameters::param_names);
+    converter->RegisterRepeatedString("param_values",
+                                      &SavedBootParameters::param_values);
+}
+
+BootParameters::BootParameters() {
+    swapBootConfigs();
+    loadParameters();
+}
+
+void BootParameters::loadParameters() {
+    std::string contents;
+    if (!ReadFileToString(kLastBootFile, &contents)) {
+        if (errno != ENOENT)
+            ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
+
+        return;
+    }
+
+    std::unique_ptr<Value> json = JSONReader::Read(contents);
+    if (json.get() == nullptr) {
+        return;
+    }
+
+    JSONValueConverter<SavedBootParameters> converter;
+    if (converter.Convert(*(json.get()), &mRawParameters)) {
+        mBrightness = mRawParameters.brightness / kFloatScaleFactor;
+        mVolume = mRawParameters.volume / kFloatScaleFactor;
+
+        if (mRawParameters.param_names.size() == mRawParameters.param_values.size()) {
+            for (size_t i = 0; i < mRawParameters.param_names.size(); i++) {
+                mParameters.push_back({
+                        .key = mRawParameters.param_names[i]->c_str(),
+                        .value = mRawParameters.param_values[i]->c_str()
+                });
+            }
+        } else {
+            ALOGW("Parameter names and values size mismatch");
+        }
+    }
+}
+
+}  // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h
new file mode 100644
index 0000000..ff3b018
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BOOTANIMATION_BOOT_PARAMETERS_H_
+#define _BOOTANIMATION_BOOT_PARAMETERS_H_
+
+#include <list>
+#include <vector>
+
+#include <base/json/json_value_converter.h>
+#include <boot_action/boot_action.h>  // libandroidthings native API.
+
+namespace android {
+
+// Provides access to the parameters set by DeviceManager.reboot().
+class BootParameters {
+public:
+    // Constructor loads the parameters for this boot and swaps the param files
+    // to clear the parameters for next boot.
+    BootParameters();
+
+    // Returns true if volume/brightness were explicitly set on reboot.
+    bool hasVolume() const { return mVolume >= 0; }
+    bool hasBrightness() const { return mBrightness >= 0; }
+
+    // Returns volume/brightness in [0,1], or -1 if unset.
+    float getVolume() const { return mVolume; }
+    float getBrightness() const { return mBrightness; }
+
+    // Returns the additional boot parameters that were set on reboot.
+    const std::vector<ABootActionParameter>& getParameters() const { return mParameters; }
+
+private:
+    // Raw boot saved_parameters loaded from .json.
+    struct SavedBootParameters {
+        int brightness;
+        int volume;
+        ScopedVector<std::string> param_names;
+        ScopedVector<std::string> param_values;
+
+        SavedBootParameters();
+        static void RegisterJSONConverter(
+                ::base::JSONValueConverter<SavedBootParameters>* converter);
+    };
+
+    void loadParameters();
+
+    float mVolume = -1.f;
+    float mBrightness = -1.f;
+    std::vector<ABootActionParameter> mParameters;
+
+    // ABootActionParameter is just a raw pointer so we need to keep the
+    // original strings around to avoid losing them.
+    SavedBootParameters mRawParameters;
+};
+
+}  // namespace android
+
+
+#endif  // _BOOTANIMATION_BOOT_PARAMETERS_H_
diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp
index 441a140..742f9c24 100644
--- a/cmds/bootanimation/iot/iotbootanimation_main.cpp
+++ b/cmds/bootanimation/iot/iotbootanimation_main.cpp
@@ -28,6 +28,7 @@
 
 #include "BootAction.h"
 #include "BootAnimationUtil.h"
+#include "BootParameters.h"
 
 using namespace android;
 using android::base::ReadFileToString;
@@ -37,7 +38,11 @@
 
 namespace {
 
-class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {public:
+class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {
+public:
+    BootActionAnimationCallbacks(std::unique_ptr<BootParameters> bootParameters)
+        : mBootParameters(std::move(bootParameters)) {}
+
     void init(const Vector<Animation::Part>&) override {
         std::string library_path("/oem/lib/");
 
@@ -51,7 +56,7 @@
         library_path += property;
 
         mBootAction = new BootAction();
-        if (!mBootAction->init(library_path)) {
+        if (!mBootAction->init(library_path, mBootParameters->getParameters())) {
             mBootAction = NULL;
         }
     };
@@ -86,6 +91,7 @@
     };
 
 private:
+    std::unique_ptr<BootParameters> mBootParameters;
     sp<BootAction> mBootAction = nullptr;
 };
 
@@ -94,9 +100,8 @@
 int main() {
     setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
 
-    // TODO(b/65462981): Should we set brightness/volume here in case the boot
-    // animation is disabled?
-    BootAction::swapBootConfigs();
+    // Clear our params for next boot no matter what.
+    std::unique_ptr<BootParameters> bootParameters(new BootParameters());
 
     if (bootAnimationDisabled()) {
         ALOGI("boot animation disabled");
@@ -108,7 +113,8 @@
     sp<ProcessState> proc(ProcessState::self());
     ProcessState::self()->startThreadPool();
 
-    sp<BootAnimation> boot = new BootAnimation(new BootActionAnimationCallbacks());
+    sp<BootAnimation> boot = new BootAnimation(
+            new BootActionAnimationCallbacks(std::move(bootParameters)));
 
     IPCThreadState::self()->joinThreadPool();
     return 0;
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index e2e1a7f..56f9512 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -44,6 +44,7 @@
     ../../core/java/android/os/IStatsManager.aidl \
     src/StatsService.cpp \
     src/AnomalyMonitor.cpp \
+    src/StatsPuller.cpp \
     src/LogEntryPrinter.cpp \
     src/LogReader.cpp \
     src/main.cpp \
diff --git a/cmds/statsd/src/StatsPuller.cpp b/cmds/statsd/src/StatsPuller.cpp
new file mode 100644
index 0000000..94e8361
--- /dev/null
+++ b/cmds/statsd/src/StatsPuller.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "StatsPuller"
+#define DEBUG true
+
+#include "StatsPuller.h"
+#include "StatsService.h"
+#include <android/os/IStatsCompanionService.h>
+#include <cutils/log.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+String16 StatsPuller::pull(int pullCode) {
+    if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+    switch (pullCode) {
+        // All stats_companion_service cases go here with fallthroughs
+        case PULL_CODE_KERNEL_WAKELOCKS: {
+            // TODO: Consider caching the statsCompanion service
+            sp <IStatsCompanionService>
+                    statsCompanion = StatsService::getStatsCompanionService();
+            String16 returned_value("");
+            Status status = statsCompanion->pullData(pullCode, &returned_value);
+            if (DEBUG) ALOGD("Finished pulling the data");
+            if (!status.isOk()) {
+                ALOGW("error pulling data of type %d", pullCode);
+            }
+            return returned_value;
+        }
+
+        // case OTHER_TYPES: etc.
+
+        default: {
+            ALOGE("invalid pull code %d", pullCode);
+            return String16("");
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/StatsPuller.h b/cmds/statsd/src/StatsPuller.h
new file mode 100644
index 0000000..05343b5
--- /dev/null
+++ b/cmds/statsd/src/StatsPuller.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STATSD_STATSPULLER_H
+#define STATSD_STATSPULLER_H
+
+#include <utils/String16.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsPuller {
+public:
+    // Enums of pulled data types (pullCodes)
+    // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
+    // TODO: pull the constant from stats_events.proto instead
+    const static int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+    StatsPuller();
+    ~StatsPuller();
+
+    static String16 pull(int pullCode);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif //STATSD_STATSPULLER_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index a28f085..ae7d66b 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -177,6 +177,9 @@
 
     if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
     // TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
+    String16 output = StatsPuller::pull(StatsPuller::PULL_CODE_KERNEL_WAKELOCKS);
+    // TODO: do something useful with the output instead of writing a string to screen.
+    ALOGD("%s", String8(output).string());
 
     return Status::ok();
 }
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index eb6aa49..a16b115 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -19,6 +19,7 @@
 
 #include "AnomalyMonitor.h"
 #include "StatsLogProcessor.h"
+#include "StatsPuller.h"
 
 #include <android/os/BnStatsManager.h>
 #include <android/os/IStatsCompanionService.h>
@@ -66,19 +67,21 @@
     /** Inform statsCompanion that statsd is ready. */
     virtual void sayHiToStatsCompanion();
 
-private:
-    sp<StatsLogProcessor> m_processor;  // Reference to the processor for updating configs.
+    // TODO: Move this to a more logical file/class
+    // TODO: Should be private. Temporarily public for testing purposes only.
+    const sp<AnomalyMonitor> mAnomalyMonitor;
 
-    const sp<AnomalyMonitor> mAnomalyMonitor;  // TODO: Move this to a more logical file/class
+    /** Fetches and returns the StatsCompanionService. */
+    static sp<IStatsCompanionService> getStatsCompanionService();
+
+ private:
+    sp<StatsLogProcessor> m_processor;  // Reference to the processor for updating configs.
 
     status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
 
     void printCmdHelp(FILE* out);
 
     status_t doLoadConfig(FILE* in);
-
-    /** Fetches the StatsCompanionService. */
-    sp<IStatsCompanionService> getStatsCompanionService();
 };
 
 // --- StatsdDeathRecipient ---
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index dffc68e..1e17895 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -23,13 +23,19 @@
 option java_outer_classname = "StatsEventProto";
 
 message StatsEvent {
-  oneof log_entry_event {
+  oneof event {
+    // Screen state change.
     ScreenStateChange screen_state_change = 2;
+    // Process state change.
     ProcessStateChange process_state_change = 1112;
   }
 }
 
+// Logs changes in screen state. This event is logged in
+// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
 message ScreenStateChange {
+  // Screen state enums follow the values defined in below file.
+  // frameworks/base/core/java/android/view/Display.java
   enum State {
     STATE_UNKNOWN = 0;
     STATE_OFF = 1;
@@ -38,20 +44,20 @@
     STATE_DOZE_SUSPEND = 4;
     STATE_VR = 5;
   }
+  // New screen state.
   optional State display_state = 1;
 }
 
+// Logs changes in process state. This event is logged in
+// frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
 message ProcessStateChange {
+  // Type of process event.
   enum State {
     START = 1;
     CRASH = 2;
   }
   optional State state = 1;
 
+  // UID associated with the package.
   optional int32 uid = 2;
-
-  optional string package_name = 1002;
-
-  optional int32 package_version = 3;
-  optional string package_version_string = 4;
 }
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 0d14a8d..80fb805 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -27,6 +27,7 @@
 import android.os.Parcelable;
 import android.transition.TransitionUtils;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
@@ -202,7 +203,8 @@
         } else {
             mTempMatrix.set(viewToGlobalMatrix);
         }
-        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
+        ViewGroup parent = (ViewGroup) sharedElement.getParent();
+        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds, parent);
     }
 
     /**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ab70f0e7..50f1f36 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -81,10 +81,10 @@
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
 import android.net.wifi.IRttManager;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.IWifiScanner;
@@ -95,6 +95,8 @@
 import android.net.wifi.aware.WifiAwareManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -603,6 +605,16 @@
                         ConnectivityThread.getInstanceLooper());
             }});
 
+        registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class,
+                new CachedServiceFetcher<WifiRttManager>() {
+                    @Override
+                    public WifiRttManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE);
+                        IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+                        return new WifiRttManager(ctx.getOuterContext(), service);
+                    }});
+
         registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
                 new CachedServiceFetcher<EthernetManager>() {
             @Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 693921d..cd9c6a3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3467,6 +3467,19 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi
+     *
+     * Note: this is a replacement for WIFI_RTT_SERVICE above. It will
+     * be renamed once final implementation in place.
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.rtt.WifiRttManager
+     * @hide
+     */
+    public static final String WIFI_RTT2_SERVICE = "rttmanager2";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.lowpan.LowpanManager} for handling management of
      * LoWPAN access.
      *
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index d3a3560..6b9c753 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -1763,21 +1763,43 @@
         return 0;
     }
 
+
     /**
      * Return a string representation, intended for logging.  Some fields will be retracted.
      */
     @Override
     public String toString() {
-        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+                /*indent=*/ null);
     }
 
     /** @hide */
     public String toInsecureString() {
-        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+                /*indent=*/ null);
     }
 
-    private String toStringInner(boolean secure, boolean includeInternalData) {
+    /** @hide */
+    public String toDumpString(String indent) {
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+    }
+
+    private void addIndentOrComma(StringBuilder sb, String indent) {
+        if (indent != null) {
+            sb.append("\n  ");
+            sb.append(indent);
+        } else {
+            sb.append(", ");
+        }
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
         final StringBuilder sb = new StringBuilder();
+
+        if (indent != null) {
+            sb.append(indent);
+        }
+
         sb.append("ShortcutInfo {");
 
         sb.append("id=");
@@ -1787,47 +1809,51 @@
         sb.append(Integer.toHexString(mFlags));
         sb.append(" [");
         if (!isEnabled()) {
-            sb.append("X");
+            sb.append("Dis");
         }
         if (isImmutable()) {
             sb.append("Im");
         }
         if (isManifestShortcut()) {
-            sb.append("M");
+            sb.append("Man");
         }
         if (isDynamic()) {
-            sb.append("D");
+            sb.append("Dyn");
         }
         if (isPinned()) {
-            sb.append("P");
+            sb.append("Pin");
         }
         if (hasIconFile()) {
-            sb.append("If");
+            sb.append("Ic-f");
         }
         if (isIconPendingSave()) {
-            sb.append("^");
+            sb.append("Pens");
         }
         if (hasIconResource()) {
-            sb.append("Ir");
+            sb.append("Ic-r");
         }
         if (hasKeyFieldsOnly()) {
-            sb.append("K");
+            sb.append("Key");
         }
         if (hasStringResourcesResolved()) {
-            sb.append("Sr");
+            sb.append("Str");
         }
         if (isReturnedByServer()) {
-            sb.append("V");
+            sb.append("Rets");
         }
         sb.append("]");
 
-        sb.append(", packageName=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("packageName=");
         sb.append(mPackageName);
 
         sb.append(", activity=");
         sb.append(mActivity);
 
-        sb.append(", shortLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("shortLabel=");
         sb.append(secure ? "***" : mTitle);
         sb.append(", resId=");
         sb.append(mTitleResId);
@@ -1835,7 +1861,9 @@
         sb.append(mTitleResName);
         sb.append("]");
 
-        sb.append(", longLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("longLabel=");
         sb.append(secure ? "***" : mText);
         sb.append(", resId=");
         sb.append(mTextResId);
@@ -1843,7 +1871,9 @@
         sb.append(mTextResName);
         sb.append("]");
 
-        sb.append(", disabledMessage=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("disabledMessage=");
         sb.append(secure ? "***" : mDisabledMessage);
         sb.append(", resId=");
         sb.append(mDisabledMessageResId);
@@ -1851,19 +1881,27 @@
         sb.append(mDisabledMessageResName);
         sb.append("]");
 
-        sb.append(", categories=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("categories=");
         sb.append(mCategories);
 
-        sb.append(", icon=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("icon=");
         sb.append(mIcon);
 
-        sb.append(", rank=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("rank=");
         sb.append(mRank);
 
         sb.append(", timestamp=");
         sb.append(mLastChangedTimestamp);
 
-        sb.append(", intents=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("intents=");
         if (mIntents == null) {
             sb.append("null");
         } else {
@@ -1885,12 +1923,15 @@
             }
         }
 
-        sb.append(", extras=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("extras=");
         sb.append(mExtras);
 
         if (includeInternalData) {
+            addIndentOrComma(sb, indent);
 
-            sb.append(", iconRes=");
+            sb.append("iconRes=");
             sb.append(mIconResId);
             sb.append("[");
             sb.append(mIconResName);
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index a83d313..d8f9567 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -20,11 +20,11 @@
   * Binder interface to communicate with the Java-based statistics service helper.
   * {@hide}
   */
-oneway interface IStatsCompanionService {
+interface IStatsCompanionService {
     /**
      * Tell statscompanion that stastd is up and running.
      */
-    void statsdReady();
+    oneway void statsdReady();
 
     /**
     * Register an alarm for anomaly detection to fire at the given timestamp (ms since epoch).
@@ -32,10 +32,10 @@
     * Uses AlarmManager.set API, so  if the timestamp is in the past, alarm fires immediately, and
     * alarm is inexact.
     */
-    void setAnomalyAlarm(long timestampMs);
+    oneway void setAnomalyAlarm(long timestampMs);
 
     /** Cancel any anomaly detection alarm. */
-    void cancelAnomalyAlarm();
+    oneway void cancelAnomalyAlarm();
 
     /**
       * Register a repeating alarm for polling to fire at the given timestamp and every
@@ -44,8 +44,11 @@
       * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
       * and alarm is inexact.
       */
-    void setPollingAlarms(long timestampMs, long intervalMs);
+    oneway void setPollingAlarms(long timestampMs, long intervalMs);
 
     /** Cancel any repeating polling alarm. */
-    void cancelPollingAlarms();
+    oneway void cancelPollingAlarms();
+
+    /** Pull the specified data. Results will be sent to statsd when complete. */
+    String pullData(int pullCode);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3c3e466..eca2fcd 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10894,6 +10894,26 @@
          */
         public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
                 "enable_deletion_helper_no_threshold_toggle";
+
+        /**
+         * The list of snooze options for notifications
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "default=60,options_array=15:30:60:120"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * default               (int)
+         * options_array         (string)
+         * </pre>
+         *
+         * All delays in integer minutes. Array order is respected.
+         * Options will be used in order up to the maximum allowed by the UI.
+         * @hide
+         */
+        public static final String NOTIFICATION_SNOOZE_OPTIONS =
+                "notification_snooze_options";
     }
 
     /**
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 2cfec30..6e69c8f 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -90,10 +90,6 @@
         }
     }
 
-    void setPos(int pos) {
-        mPos = pos - mTextStart;
-    }
-
     /**
      * Analyzes text for bidirectional runs.  Allocates working buffers.
      */
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index 3306a50..084b79d 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -101,7 +101,7 @@
 
         ImageView copy = new ImageView(view.getContext());
         copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
-        Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+        Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot);
         if (bitmap != null) {
             copy.setImageBitmap(bitmap);
         }
@@ -156,11 +156,18 @@
      *               returning.
      * @param bounds The bounds of the bitmap in the destination coordinate system (where the
      *               view should be presented. Typically, this is matrix.mapRect(viewBounds);
+     * @param sceneRoot A ViewGroup that is attached to the window to temporarily contain the view
+     *                  if it isn't attached to the window.
      * @return A bitmap of the given view or null if bounds has no width or height.
      */
-    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
-        if (!view.isAttachedToWindow()) {
-            return null;
+    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
+            ViewGroup sceneRoot) {
+        final boolean addToOverlay = !view.isAttachedToWindow();
+        if (addToOverlay) {
+            if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
+                return null;
+            }
+            sceneRoot.getOverlay().add(view);
         }
         Bitmap bitmap = null;
         int bitmapWidth = Math.round(bounds.width());
@@ -181,6 +188,9 @@
             node.end(canvas);
             bitmap = ThreadedRenderer.createHardwareBitmap(node, bitmapWidth, bitmapHeight);
         }
+        if (addToOverlay) {
+            sceneRoot.getOverlay().remove(view);
+        }
         return bitmap;
     }
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index eb639bd..6e0ba341 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@
     private static final boolean DEBUG = false;
 
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
-    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
 
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
@@ -1585,8 +1585,8 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
                     mChooserActivity.unbindService(this);
-                    destroy();
                     mChooserActivity.mServiceConnections.remove(this);
+                    destroy();
                 }
             }
         }
@@ -1602,7 +1602,6 @@
                 }
 
                 mChooserActivity.unbindService(this);
-                destroy();
                 mChooserActivity.mServiceConnections.remove(this);
                 if (mChooserActivity.mServiceConnections.isEmpty()) {
                     mChooserActivity.mChooserHandler.removeMessages(
@@ -1610,6 +1609,7 @@
                     mChooserActivity.sendVoiceChoicesIfNeeded();
                 }
                 mConnectedComponent = null;
+                destroy();
             }
         }
 
diff --git a/core/res/res/drawable/ic_ab_back_material_settings.xml b/core/res/res/drawable/ic_ab_back_material_settings.xml
new file mode 100644
index 0000000..7325a41
--- /dev/null
+++ b/core/res/res/drawable/ic_ab_back_material_settings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@color/white"
+        android:pathData="M20,11H7.62l4.88,-4.88a0.996,0.996 0,1 0,-1.41 -1.41l-6.94,6.94c-0.2,0.2 -0.2,0.51 0,0.71l6.94,6.94a0.996,0.996 0,1 0,1.41 -1.41L7.62,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
+</vector>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 9dafa7a..9bea3ee 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1336,6 +1336,7 @@
 
     <!-- Default theme for Settings and activities launched from Settings. -->
     <style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar">
+        <item name="homeAsUpIndicator">@drawable/ic_ab_back_material_settings</item>
         <item name="colorPrimary">@color/primary_material_settings_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_material_settings_light</item>
         <item name="colorSecondary">@color/secondary_material_settings_light</item>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8395638..d9bcc57d 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -277,6 +277,7 @@
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                     Settings.Global.NSD_ON,
                     Settings.Global.NTP_SERVER,
                     Settings.Global.NTP_TIMEOUT,
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index e3527e3..43fd270 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -658,6 +658,14 @@
      *    float confidence = floatDepthBuffer.get();
      * </pre>
      *
+     * For camera devices that support the
+     * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}
+     * capability, DEPTH_POINT_CLOUD coordinates have units of meters, and the coordinate system is
+     * defined by the camera's pose transforms:
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_TRANSLATION} and
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_ROTATION}. That means the origin is
+     * the optical center of the camera device, and the positive Z axis points along the camera's optical axis,
+     * toward the scene.
      */
     public static final int DEPTH_POINT_CLOUD = 0x101;
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 186b265..dab7632a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4119,7 +4119,15 @@
                         Log.w(TAG, "updateAudioPortCache: listAudioPatches failed");
                         return status;
                     }
-                } while (patchGeneration[0] != portGeneration[0]);
+                    // Loop until patch generation is the same as port generation unless audio ports
+                    // and audio patches are not null.
+                } while (patchGeneration[0] != portGeneration[0]
+                        && (ports == null || patches == null));
+                // If the patch generation doesn't equal port generation, return ERROR here in case
+                // of mismatch between audio ports and audio patches.
+                if (patchGeneration[0] != portGeneration[0]) {
+                    return ERROR;
+                }
 
                 for (int i = 0; i < newPatches.size(); i++) {
                     for (int j = 0; j < newPatches.get(i).sources().length; j++) {
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index c152245..ac3904a 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import java.util.ArrayList;
@@ -30,6 +31,7 @@
 
 class AudioPortEventHandler {
     private Handler mHandler;
+    private HandlerThread mHandlerThread;
     private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
             new ArrayList<AudioManager.OnAudioPortUpdateListener>();
 
@@ -40,6 +42,8 @@
     private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
     private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
 
+    private static final long RESCHEDULE_MESSAGE_DELAY_MS = 100;
+
     /**
      * Accessed by native methods: JNI Callback context.
      */
@@ -51,11 +55,12 @@
             if (mHandler != null) {
                 return;
             }
-            // find the looper for our new event handler
-            Looper looper = Looper.getMainLooper();
+            // create a new thread for our new event handler
+            mHandlerThread = new HandlerThread(TAG);
+            mHandlerThread.start();
 
-            if (looper != null) {
-                mHandler = new Handler(looper) {
+            if (mHandlerThread.getLooper() != null) {
+                mHandler = new Handler(mHandlerThread.getLooper()) {
                     @Override
                     public void handleMessage(Message msg) {
                         ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
@@ -86,6 +91,12 @@
                         if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
                             int status = AudioManager.updateAudioPortCache(ports, patches, null);
                             if (status != AudioManager.SUCCESS) {
+                                // Since audio ports and audio patches are not null, the return
+                                // value could be ERROR due to inconsistency between port generation
+                                // and patch generation. In this case, we need to reschedule the
+                                // message to make sure the native callback is done.
+                                sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+                                        RESCHEDULE_MESSAGE_DELAY_MS);
                                 return;
                             }
                         }
@@ -132,6 +143,9 @@
     @Override
     protected void finalize() {
         native_finalize();
+        if (mHandlerThread.isAlive()) {
+            mHandlerThread.quit();
+        }
     }
     private native void native_finalize();
 
@@ -168,6 +182,10 @@
             Handler handler = eventHandler.handler();
             if (handler != null) {
                 Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                if (what != AUDIOPORT_EVENT_NEW_LISTENER) {
+                    // Except AUDIOPORT_EVENT_NEW_LISTENER, we can only respect the last message.
+                    handler.removeMessages(what);
+                }
                 handler.sendMessage(m);
             }
         }
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 80fd5c0..17b2326 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -845,6 +845,33 @@
         return MtpConstants.RESPONSE_OK;
     }
 
+    private int moveObject(int handle, int newParent, String newPath) {
+        String[] whereArgs = new String[] {  Integer.toString(handle) };
+
+        // do not allow renaming any of the special subdirectories
+        if (isStorageSubDirectory(newPath)) {
+            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+        }
+
+        // update database
+        ContentValues values = new ContentValues();
+        values.put(Files.FileColumns.DATA, newPath);
+        values.put(Files.FileColumns.PARENT, newParent);
+        int updated = 0;
+        try {
+            // note - we are relying on a special case in MediaProvider.update() to update
+            // the paths for all children in the case where this is a directory.
+            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
+        }
+        if (updated == 0) {
+            Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        }
+        return MtpConstants.RESPONSE_OK;
+    }
+
     private int setObjectProperty(int handle, int property,
                             long intValue, String stringValue) {
         switch (property) {
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 5b874cd..7225f10 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -68,6 +68,7 @@
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
 static jmethodID method_deleteFile;
+static jmethodID method_moveObject;
 static jmethodID method_getObjectReferences;
 static jmethodID method_setObjectReferences;
 static jmethodID method_sessionStarted;
@@ -178,6 +179,9 @@
 
     virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property);
 
+    virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+                                            MtpString& newPath);
+
     virtual void                    sessionStarted();
 
     virtual void                    sessionEnded();
@@ -995,6 +999,18 @@
     return result;
 }
 
+MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+        MtpString &newPath) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jstring stringValue = env->NewStringUTF((const char *) newPath);
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
+                (jint)handle, (jint)newParent, stringValue);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(stringValue);
+    return result;
+}
+
 struct PropertyTableEntry {
     MtpObjectProperty   property;
     int                 type;
@@ -1360,6 +1376,11 @@
         ALOGE("Can't find deleteFile");
         return -1;
     }
+    method_moveObject = env->GetMethodID(clazz, "moveObject", "(IILjava/lang/String;)I");
+    if (method_moveObject == NULL) {
+        ALOGE("Can't find moveObject");
+        return -1;
+    }
     method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
     if (method_getObjectReferences == NULL) {
         ALOGE("Can't find getObjectReferences");
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
index 4ca5bef..b1f3f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -215,6 +215,13 @@
     }
 
     /**
+     * Calls {@code PackageManager.setComponentEnabledSetting}
+     */
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+        mPm.setComponentEnabledSetting(componentName, newState, flags);
+    }
+
+    /**
      * Calls {@code PackageManager.getApplicationInfo}
      */
     public ApplicationInfo getApplicationInfo(String packageName, int flags)
@@ -228,5 +235,12 @@
     public CharSequence getApplicationLabel(ApplicationInfo info) {
         return mPm.getApplicationLabel(info);
     }
+
+    /**
+     * Calls {@code PackageManager.queryBroadcastReceivers}
+     */
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return mPm.queryBroadcastReceivers(intent, flags);
+    }
 }
 
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6da67df..1be0645 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -193,4 +193,7 @@
 
     <!-- Default for Settings.Secure.BACKUP_MANAGER_CONSTANTS -->
     <string name="def_backup_manager_constants"></string>
+
+    <!-- Default setting for Settings.Global.MOBILE_DATA_ALWAYS_ON -->
+    <bool name="def_mobile_data_always_on">true</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a463db6..36f9b84 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 149;
+            private static final int SETTINGS_VERSION = 150;
 
             private final int mUserId;
 
@@ -3470,9 +3470,25 @@
                                     true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
                     }
-
                     currentVersion = 149;
                 }
+
+                if (currentVersion == 149) {
+                    // Version 150: Set a default value for mobile data always on
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting currentSetting = globalSettings.getSettingLocked(
+                            Settings.Global.MOBILE_DATA_ALWAYS_ON);
+                    if (currentSetting.isNull()) {
+                        globalSettings.insertSettingLocked(
+                                Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                                getContext().getResources().getBoolean(
+                                        R.bool.def_mobile_data_always_on) ? "1" : "0",
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 150;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b758e7f5..eab42da 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -96,6 +96,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.CREATE_USERS" />
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
+    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 87f6306..8a1e0b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -422,4 +422,14 @@
          increase the rate of unintentional unlocks. -->
     <bool name="config_lockscreenAntiFalsingClassifierEnabled">true</bool>
 
+    <!-- Snooze: default notificaiton snooze time. -->
+    <integer name="config_notification_snooze_time_default">60</integer>
+
+    <!-- Snooze: List of snooze values in integer minutes. -->
+    <integer-array name="config_notification_snooze_times">
+        <item>15</item>
+        <item>30</item>
+        <item>60</item>
+        <item>120</item>
+    </integer-array>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2148c80..e5f8029 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -82,10 +82,10 @@
 
     <!-- Accessibility actions for the notification menu -->
     <item type="id" name="action_snooze_undo"/>
-    <item type="id" name="action_snooze_15_min"/>
-    <item type="id" name="action_snooze_30_min"/>
-    <item type="id" name="action_snooze_1_hour"/>
-    <item type="id" name="action_snooze_2_hours"/>
+    <item type="id" name="action_snooze_shorter"/>
+    <item type="id" name="action_snooze_short"/>
+    <item type="id" name="action_snooze_long"/>
+    <item type="id" name="action_snooze_longer"/>
     <item type="id" name="action_snooze_assistant_suggestion_1"/>
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..54e9ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,10 @@
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
@@ -29,11 +31,13 @@
 import android.content.res.Resources;
 import android.graphics.Typeface;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.SpannableString;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -51,11 +55,14 @@
 public class NotificationSnooze extends LinearLayout
         implements NotificationGuts.GutsContent, View.OnClickListener {
 
+    private static final String TAG = "NotificationSnooze";
     /**
      * If this changes more number increases, more assistant action resId's should be defined for
      * accessibility purposes, see {@link #setSnoozeOptions(List)}
      */
     private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+    private static final String KEY_DEFAULT_SNOOZE = "default";
+    private static final String KEY_OPTIONS = "options_array";
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -72,9 +79,29 @@
     private boolean mSnoozing;
     private boolean mExpanded;
     private AnimatorSet mExpandAnimation;
+    private KeyValueListParser mParser;
+
+    private final static int[] sAccessibilityActions = {
+            R.id.action_snooze_shorter,
+            R.id.action_snooze_short,
+            R.id.action_snooze_long,
+            R.id.action_snooze_longer,
+    };
 
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mParser = new KeyValueListParser(',');
+    }
+
+    @VisibleForTesting
+    SnoozeOption getDefaultOption()
+    {
+        return mDefaultOption;
+    }
+
+    @VisibleForTesting
+    void setKeyValueListParser(KeyValueListParser parser) {
+        mParser = parser;
     }
 
     @Override
@@ -172,17 +199,49 @@
         mSbn = sbn;
     }
 
-    private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+    @VisibleForTesting
+    ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+        final Resources resources = getContext().getResources();
         ArrayList<SnoozeOption> options = new ArrayList<>();
+        try {
+            final String config = Settings.Global.getString(getContext().getContentResolver(),
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+            mParser.setString(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Bad snooze constants");
+        }
 
-        options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
-        options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
-        mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
-        options.add(mDefaultOption);
-        options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+        final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+                resources.getInteger(R.integer.config_notification_snooze_time_default));
+        final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+                resources.getIntArray(R.array.config_notification_snooze_times));
+
+        for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+            int snoozeTime = snoozeTimes[i];
+            SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+            if (i == 0 || snoozeTime == defaultSnooze) {
+                mDefaultOption = option;
+            }
+            options.add(option);
+        }
         return options;
     }
 
+    @VisibleForTesting
+    int[] parseIntArray(final String key, final int[] defaultArray) {
+        final String value = mParser.getString(key, null);
+        if (value != null) {
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
+        } else {
+            return defaultArray;
+        }
+    }
+
     private SnoozeOption createOption(int minutes, int accessibilityActionId) {
         Resources res = getResources();
         boolean showInHours = minutes >= 60;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
new file mode 100644
index 0000000..6b31c96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.testing.UiThreadTest;
+import android.util.KeyValueListParser;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class NotificationSnoozeTest extends SysuiTestCase {
+    private static final int RES_DEFAULT = 2;
+    private static final int[] RES_OPTIONS = {1, 2, 3};
+    private NotificationSnooze mNotificationSnooze;
+    private KeyValueListParser mMockParser;
+
+    @Before
+    public void setUp() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, null);
+        TestableResources resources = mContext.getOrCreateTestableResources();
+        resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
+        resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
+        mNotificationSnooze = new NotificationSnooze(mContext, null);
+        mMockParser = mock(KeyValueListParser.class);
+    }
+
+    @Test
+    public void testParseIntArrayNull() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn(null);
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayLeadingSep() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn(":4:5:6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayEmptyItem() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4::6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayTrailingSep() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6:");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(3, result.length);
+        assertEquals(4, result[0]);  // respect order
+        assertEquals(5, result[1]);
+        assertEquals(6, result[2]);
+    }
+
+    @Test
+    public void testParseIntArrayGoodData() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(3, result.length);
+        assertEquals(4, result[0]);  // respect order
+        assertEquals(5, result[1]);
+        assertEquals(6, result[2]);
+    }
+
+    @Test
+    public void testGetOptionsWithNoConfig() throws Exception {
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(3, result.size());
+        assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithInvalidConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "this is garbage");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(3, result.size());
+        assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithValidDefault() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=10,options_array=4:5:6:7");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertNotNull(mNotificationSnooze.getDefaultOption());  // pick one
+    }
+
+    @Test
+    public void testGetOptionsWithValidConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=6,options_array=4:5:6:7");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(4, result.size());
+        assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(7, result.get(3).getMinutesToSnoozeFor());
+        assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithLongConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertTrue(result.size() > 3);
+        assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+    }
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 787d363..bed8e3f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3851,7 +3851,7 @@
     // Tag of a field for the length of the filter text
     FIELD_AUTOFILL_FILTERTEXT_LEN = 911;
 
-    // An autofill authentification succeeded
+    // An autofill authentication succeeded
     // Package: Package of app that was autofilled
     AUTOFILL_AUTHENTICATED = 912;
 
@@ -4462,19 +4462,19 @@
     // OS: O MR
     FIELD_AUTOFILL_PREVIOUS_LENGTH = 1125;
 
-    // An autofill dataset authentification succeeded
+    // An autofill dataset authentication succeeded
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     AUTOFILL_DATASET_AUTHENTICATED = 1126;
 
-    // An autofill service provided an invalid dataset authentification
+    // An autofill service provided an invalid dataset authentication
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
 
-    // An autofill service provided an invalid authentification extra
+    // An autofill service provided an invalid authentication extra
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
@@ -4636,6 +4636,11 @@
     // OS: P
     DIALOG_ENABLE_ADB = 1161;
 
+    // OPEN: Settings > Developer options > Revoke USB debugging authorizations > Info dialog
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_CLEAR_ADB_KEYS = 1162;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 2f31fc3..f4dbb5a 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1987,7 +1987,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[] { pkgName };
                 }
@@ -1995,7 +1995,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -2005,7 +2005,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -2037,7 +2038,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (appGetsFullBackup(app)
@@ -2054,7 +2055,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -2078,8 +2080,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 8b4cc7f..14a008e 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -1188,7 +1188,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[]{pkgName};
                 }
@@ -1196,7 +1196,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -1206,7 +1206,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -1238,7 +1239,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (AppBackupUtils.appGetsFullBackup(app)
@@ -1256,7 +1257,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -1280,8 +1282,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 9aae384..7a0173f 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@
     private class TransportConnection implements ServiceConnection {
 
         // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
-        private IBackupTransport mBinder;
+        private volatile IBackupTransport mBinder;
         private final List<TransportReadyCallback> mListeners = new ArrayList<>();
-        private String mTransportName;
+        private volatile String mTransportName;
 
         private final ComponentName mTransportComponent;
 
@@ -426,25 +426,24 @@
                     + rebindTimeout + "ms");
         }
 
+        // Intentionally not synchronized -- the variable is volatile and changes to its value
+        // are inside synchronized blocks, providing a memory sync barrier; and this method
+        // does not touch any other state protected by that lock.
         private IBackupTransport getBinder() {
-            synchronized (mTransportLock) {
-                return mBinder;
-            }
+            return mBinder;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private String getName() {
-            synchronized (mTransportLock) {
-                return mTransportName;
-            }
+            return mTransportName;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private void bindIfUnbound() {
-            synchronized (mTransportLock) {
-                if (mBinder == null) {
-                    Slog.d(TAG,
-                            "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                    bindToTransport(mTransportComponent, this);
-                }
+            if (mBinder == null) {
+                Slog.d(TAG,
+                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
+                bindToTransport(mTransportComponent, this);
             }
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bc20a2e..c17583f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4158,7 +4158,8 @@
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
         synchronized (mConnectedDevices) {
-            if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
                     && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                     && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
                     && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ac80794..78aa2f9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@
      * @param uid Uid to check against for removal of a job.
      *
      */
-    public void cancelJobsForUid(int uid, String reason) {
+    public boolean cancelJobsForUid(int uid, String reason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
-            return;
+            return false;
         }
+
+        boolean jobsCanceled = false;
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i=0; i<jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.get(i);
                 cancelJobImplLocked(toRemove, null, reason);
+                jobsCanceled = true;
             }
         }
+        return jobsCanceled;
     }
 
     /**
@@ -816,13 +820,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public void cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
                 cancelJobImplLocked(toCancel, null, "cancel() called by app");
             }
+            return (toCancel != null);
         }
     }
 
@@ -2147,6 +2152,39 @@
         return 0;
     }
 
+    // Shell command infrastructure: cancel a scheduled job
+    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+            boolean hasJobId, int jobId) {
+        if (DEBUG) {
+            Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+        }
+
+        int pkgUid = -1;
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            pkgUid = pm.getPackageUid(pkgName, 0, userId);
+        } catch (RemoteException e) { /* can't happen */ }
+
+        if (pkgUid < 0) {
+            pw.println("Package " + pkgName + " not found.");
+            return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+        }
+
+        if (!hasJobId) {
+            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+                pw.println("No matching jobs found.");
+            }
+        } else {
+            pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+            if (!cancelJob(pkgUid, jobId)) {
+                pw.println("No matching job found.");
+            }
+        }
+
+        return 0;
+    }
+
     void setMonitorBattery(boolean enabled) {
         synchronized (mLock) {
             if (mBatteryController != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index a53c088..d630aab 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@
                     return runJob(pw);
                 case "timeout":
                     return timeout(pw);
+                case "cancel":
+                    return cancelJob(pw);
                 case "monitor-battery":
                     return monitorBattery(pw);
                 case "get-battery-seq":
@@ -205,6 +207,42 @@
         }
     }
 
+    private int cancelJob(PrintWriter pw) throws Exception {
+        checkPermission("cancel jobs");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId < 0) {
+            pw.println("Error: must specify a concrete user ID");
+            return -1;
+        }
+
+        final String pkgName = getNextArg();
+        final String jobIdStr = getNextArg();
+        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int monitorBattery(PrintWriter pw) throws Exception {
         checkPermission("change battery monitoring");
         String opt = getNextArgRequired();
@@ -315,6 +353,12 @@
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
+        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+        pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
+        pw.println("    by that package will be canceled.  USE WITH CAUTION.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  monitor-battery [on|off]");
         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
         pw.println("    on makes get-battery-seq useful.");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 6f70f4c..9309f18 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1186,9 +1186,7 @@
         final int size = shortcuts.size();
         for (int i = 0; i < size; i++) {
             final ShortcutInfo si = shortcuts.valueAt(i);
-            pw.print(prefix);
-            pw.print("    ");
-            pw.println(si.toInsecureString());
+            pw.println(si.toDumpString(prefix + "    "));
             if (si.getBitmapPath() != null) {
                 final long len = new File(si.getBitmapPath()).length();
                 pw.print(prefix);
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 1a62662..05fd248 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -30,8 +30,12 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
 import com.android.server.SystemService;
 
+import java.util.Map;
+
 /**
  * Helper service for statsd (the native stats management service in cmds/statsd/).
  * Used for registering and receiving alarms on behalf of statsd.
@@ -157,7 +161,44 @@
         }
     }
 
-    @Override
+    // These values must be kept in sync with cmd/statsd/StatsPuller.h.
+    // TODO: pull the constant from stats_events.proto instead
+    private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    @Override // Binder call
+    public String pullData(int pullCode) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+
+        StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+        switch (pullCode) {
+            case PULL_CODE_KERNEL_WAKELOCKS:
+                final KernelWakelockStats wakelockStats =
+                        mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+
+                for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+                    String name = ent.getKey();
+                    KernelWakelockStats.Entry kws = ent.getValue();
+                    s.append("Wakelock ")
+                            .append(name)
+                            .append(", time=")
+                            .append(kws.mTotalTime)
+                            .append(", count=")
+                            .append(kws.mCount)
+                            .append('\n');
+                }
+                break;
+            default:
+                Slog.w(TAG, "No such pollable data as " + pullCode);
+                return null;
+        }
+        return s.toString();
+    }
+
+    @Override // Binder call
     public void statsdReady() {
         enforceCallingPermission();
         if (DEBUG) Slog.d(TAG, "learned that statsdReady");
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 8f50a39..5721415 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,8 @@
 
             int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
                     DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
                     null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4308ff9..65278837 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -923,7 +923,9 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        mAnimationBackgroundSurface.hide();
+        if (mAnimationBackgroundSurface != null) {
+            mAnimationBackgroundSurface.hide();
+        }
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1c06662..1c0e0cd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1092,6 +1092,7 @@
                 if (!disableRtt) {
                     traceBeginAndSlog("StartWifiRtt");
                     mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                    mSystemServiceManager.startService("com.android.server.wifi.rtt.RttService");
                     traceEnd();
                 }
 
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 61415b5..fe27537 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,6 +165,12 @@
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
+            // We do not need to verify below extras if the result is not success.
+            if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
+                    intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+                    MbmsDownloadSession.RESULT_CANCELLED)) {
+                return true;
+            }
             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
                 return false;
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 9a01ed0..8529f52 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -62,12 +63,6 @@
             throw new RuntimeException("bad locales length " + newLocales.size());
         }
 
-        for (Locale l : newLocales) {
-            if (!newNames.containsKey(l)) {
-                throw new IllegalArgumentException("A name must be provided for each locale");
-            }
-        }
-
         names = new HashMap(newNames.size());
         names.putAll(newNames);
         className = newClassName;
@@ -127,7 +122,7 @@
      * Get the user-displayable name for this cell-broadcast service corresponding to the
      * provided {@link Locale}.
      * @param locale The {@link Locale} in which you want the name of the service. This must be a
-     *               value from the list returned by {@link #getLocales()} -- an
+     *               value from the set returned by {@link #getNamedContentLocales()} -- an
      *               {@link java.util.NoSuchElementException} may be thrown otherwise.
      * @return The {@link CharSequence} providing the name of the service in the given
      *         {@link Locale}
@@ -140,6 +135,17 @@
     }
 
     /**
+     * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+     * associated with them. The user-displayable name associated with any {@link Locale} in this
+     * set can be retrieved with {@link #getNameForLocale(Locale)}.
+     * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+     * content name in that locale.
+     */
+    public @NonNull Set<Locale> getNamedContentLocales() {
+        return Collections.unmodifiableSet(names.keySet());
+    }
+
+    /**
      * The class name for this service - used to categorize and filter
      */
     public String getServiceClassName() {
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
index 8fb27b2..a43f122 100644
--- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
+++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@
 
     /**
      * The MBMS middleware should send this when a download of single file has completed or
-     * failed. Mandatory extras are
+     * failed. The only mandatory extra is
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+     * and the following are required when the download has completed:
      * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
      * {@link #EXTRA_TEMP_LIST}
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 5f586a1..627a231 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1246,7 +1246,7 @@
         if (kIsDebug) {
             printf("Adding 9-patch info...\n");
         }
-        strcpy((char*)unknowns[p_index].name, "npTc");
+        memcpy((char*)unknowns[p_index].name, "npTc", 5);
         unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
         unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
         // TODO: remove the check below when everything works
@@ -1254,7 +1254,7 @@
 
         // automatically generated 9 patch outline data
         int chunk_size = sizeof(png_uint_32) * 6;
-        strcpy((char*)unknowns[o_index].name, "npOl");
+        memcpy((char*)unknowns[o_index].name, "npOl", 5);
         unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
         png_byte outputData[chunk_size];
         memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -1266,7 +1266,7 @@
         // optional optical inset / layout bounds data
         if (imageInfo.haveLayoutBounds) {
             int chunk_size = sizeof(png_uint_32) * 4;
-            strcpy((char*)unknowns[b_index].name, "npLb");
+            memcpy((char*)unknowns[b_index].name, "npLb", 5);
             unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
             memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
             unknowns[b_index].size = chunk_size;
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index b80780e..6f2b865 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,11 +20,15 @@
 #include "ValueVisitor.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
 #include "io/BigBufferInputStream.h"
 #include "io/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
+using xml::XmlResource;
+
 std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
                                                       const android::StringPiece& path) {
   Source source(path);
@@ -52,6 +56,7 @@
   if (!parser.Parse()) {
     return {};
   }
+
   return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
 }
 
@@ -63,7 +68,7 @@
 
 bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
                                const TableFlattenerOptions& options, FilterChain* filters,
-                               IArchiveWriter* writer) {
+                               IArchiveWriter* writer, XmlResource* manifest) {
   std::set<std::string> referenced_resources;
   // List the files being referenced in the resource table.
   for (auto& pkg : split_table->packages) {
@@ -119,6 +124,20 @@
         return false;
       }
 
+    } else if (manifest != nullptr && path == "AndroidManifest.xml") {
+      BigBuffer buffer(8192);
+      XmlFlattener xml_flattener(&buffer, {});
+      if (!xml_flattener.Consume(context, manifest)) {
+        context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
+        return false;
+      }
+
+      uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+      io::BigBufferInputStream manifest_buffer_in(&buffer);
+      if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
+                                        writer)) {
+        return false;
+      }
     } else {
       uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
       if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
@@ -129,4 +148,26 @@
   return true;
 }
 
+std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
+  IDiagnostics* diag = context->GetDiagnostics();
+
+  io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
+    return {};
+  }
+
+  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+  if (manifest_data == nullptr) {
+    diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
+    return {};
+  }
+
+  std::unique_ptr<xml::XmlResource> manifest =
+      xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
+  }
+  return manifest;
+}
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index dacd0c2..d779b7e 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -25,17 +25,17 @@
 #include "flatten/TableFlattener.h"
 #include "io/ZipArchive.h"
 #include "unflatten/BinaryResourceParser.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
 /** Info about an APK loaded in memory. */
 class LoadedApk {
  public:
-  LoadedApk(
-      const Source& source,
-      std::unique_ptr<io::IFileCollection> apk,
-      std::unique_ptr<ResourceTable> table)
-      : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
+  LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+            std::unique_ptr<ResourceTable> table)
+      : source_(source), apk_(std::move(apk)), table_(std::move(table)) {
+  }
 
   io::IFileCollection* GetFileCollection() { return apk_.get(); }
 
@@ -51,13 +51,20 @@
                               IArchiveWriter* writer);
 
   /**
-   * Writes the APK on disk at the given path, while also removing the resource
-   * files that are not referenced in the resource table. The provided filter
-   * chain is applied to each entry in the APK file.
+   * Writes the APK on disk at the given path, while also removing the resource files that are not
+   * referenced in the resource table. The provided filter chain is applied to each entry in the APK
+   * file.
+   *
+   * If the manifest is also provided, it will be written to the new APK file, otherwise the
+   * original manifest will be written. The manifest is only required if the contents of the new APK
+   * have been modified in a way that require the AndroidManifest.xml to also be modified.
    */
   virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
                               const TableFlattenerOptions& options, FilterChain* filters,
-                              IArchiveWriter* writer);
+                              IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+
+  /** Inflates the AndroidManifest.xml file from the APK. */
+  std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
 
   static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
                                                     const android::StringPiece& path);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 704faee..56b61d0 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -283,24 +283,8 @@
 
 bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
                                 OptimizeOptions* out_options) {
-  io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
-  if (manifest_file == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                     << "missing AndroidManifest.xml");
-    return false;
-  }
-
-  std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
-  if (data == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
-                                     << "failed to open file");
-    return false;
-  }
-
-  std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
-      data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
+  std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
   if (manifest == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
     return false;
   }
 
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 6d6147d..33122dc 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -538,7 +538,7 @@
     if (kDebug) {
       diag->Note(DiagMessage() << "adding 9-patch info..");
     }
-    strcpy((char*)unknowns[pIndex].name, "npTc");
+    memcpy((char*)unknowns[pIndex].name, "npTc", 5);
     unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
     unknowns[pIndex].size = info->info9Patch.serializedSize();
     // TODO: remove the check below when everything works
@@ -546,7 +546,7 @@
 
     // automatically generated 9 patch outline data
     int chunkSize = sizeof(png_uint_32) * 6;
-    strcpy((char*)unknowns[oIndex].name, "npOl");
+    memcpy((char*)unknowns[oIndex].name, "npOl", 5);
     unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
     png_byte outputData[chunkSize];
     memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -558,7 +558,7 @@
     // optional optical inset / layout bounds data
     if (info->haveLayoutBounds) {
       int chunkSize = sizeof(png_uint_32) * 4;
-      strcpy((char*)unknowns[bIndex].name, "npLb");
+      memcpy((char*)unknowns[bIndex].name, "npLb", 5);
       unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
       memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
       unknowns[bIndex].size = chunkSize;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 9d6d328..a79a577 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -27,6 +27,7 @@
 
 #include "ConfigDescription.h"
 #include "Diagnostics.h"
+#include "ResourceUtils.h"
 #include "io/File.h"
 #include "io/FileSystem.h"
 #include "io/StringInputStream.h"
@@ -329,15 +330,32 @@
   // TODO: Validate all references in the configuration are valid. It should be safe to assume from
   // this point on that any references from one section to another will be present.
 
+  // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
+  // see: https://developer.android.com/google/play/publishing/multiple-apks.html
+  //
+  // For now, make sure the version codes are unique.
+  std::vector<Artifact>& artifacts = config.artifacts;
+  std::sort(artifacts.begin(), artifacts.end());
+  if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
+    diag_->Error(DiagMessage() << "Configuration has duplicate versions");
+    return {};
+  }
+
   return {config};
 }
 
 ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
     [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+  // This will be incremented later so the first version will always be different to the base APK.
+  int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
+
   Artifact artifact{};
+  Maybe<int> version;
   for (const auto& attr : root_element->attributes) {
     if (attr.name == "name") {
       artifact.name = attr.value;
+    } else if (attr.name == "version") {
+      version = std::stoi(attr.value);
     } else if (attr.name == "abi-group") {
       artifact.abi_group = {attr.value};
     } else if (attr.name == "screen-density-group") {
@@ -355,6 +373,9 @@
                                << attr.value);
     }
   }
+
+  artifact.version = (version) ? version.value() : current_version + 1;
+
   config->artifacts.push_back(artifact);
   return true;
 };
@@ -499,11 +520,11 @@
       AndroidSdk entry;
       for (const auto& attr : child->attributes) {
         if (attr.name == "minSdkVersion") {
-          entry.min_sdk_version = {attr.value};
+          entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else if (attr.name == "targetSdkVersion") {
-          entry.target_sdk_version = {attr.value};
+          entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else if (attr.name == "maxSdkVersion") {
-          entry.max_sdk_version = {attr.value};
+          entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else {
           diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
         }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 9bc9081..c5d3284 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -17,6 +17,7 @@
 #ifndef AAPT2_CONFIGURATION_H
 #define AAPT2_CONFIGURATION_H
 
+#include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -41,6 +42,12 @@
 struct Artifact {
   /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
   Maybe<std::string> name;
+  /**
+   * Value to add to the base Android manifest versionCode. If it is not present in the
+   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
+   * a value, artifacts are a 1 based index.
+   */
+  int version;
   /** If present, uses the ABI group with this name. */
   Maybe<std::string> abi_group;
   /** If present, uses the screen density group with this name. */
@@ -60,6 +67,15 @@
 
   /** Convert an artifact name template into a name string based on configuration contents. */
   Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
+
+  bool operator<(const Artifact& rhs) const {
+    // TODO(safarmer): Order by play store multi-APK requirements.
+    return version < rhs.version;
+  }
+
+  bool operator==(const Artifact& rhs) const  {
+    return version == rhs.version;
+  }
 };
 
 /** Enumeration of currently supported ABIs. */
@@ -103,14 +119,14 @@
 };
 
 struct AndroidSdk {
-  Maybe<std::string> min_sdk_version;
-  Maybe<std::string> target_sdk_version;
-  Maybe<std::string> max_sdk_version;
+  Maybe<int> min_sdk_version;
+  Maybe<int> target_sdk_version;
+  Maybe<int> max_sdk_version;
   Maybe<AndroidManifest> manifest;
 
-  static AndroidSdk ForMinSdk(std::string min_sdk) {
+  static AndroidSdk ForMinSdk(int min_sdk) {
     AndroidSdk sdk;
-    sdk.min_sdk_version = {std::move(min_sdk)};
+    sdk.min_sdk_version = min_sdk;
     return sdk;
   }
 
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 7ffb3d5..3654901 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -24,6 +24,15 @@
 #include "xml/XmlDom.h"
 
 namespace aapt {
+
+namespace configuration {
+void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
+  *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+      << ", target=" << sdk.target_sdk_version.value_or_default(-1)
+      << ", max=" << sdk.max_sdk_version.value_or_default(-1);
+}
+}  // namespace configuration
+
 namespace {
 
 using ::android::ResTable_config;
@@ -76,9 +85,9 @@
     </locale-group>
     <android-sdk-group label="v19">
       <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
+          minSdkVersion="19"
+          targetSdkVersion="24"
+          maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
@@ -156,7 +165,7 @@
 
   EXPECT_EQ(1ul, value.android_sdk_groups.size());
   EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version);
-  EXPECT_EQ("v19", value.android_sdk_groups["v19"].min_sdk_version.value());
+  EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value());
 
   EXPECT_EQ(1ul, value.gl_texture_groups.size());
   EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
@@ -174,55 +183,117 @@
 }
 
 TEST_F(ConfigurationParserTest, ArtifactAction) {
-  static constexpr const char* xml = R"xml(
+  PostProcessingConfiguration config;
+  {
+    const auto doc = test::BuildXmlDom(R"xml(
+      <artifact
+          abi-group="arm"
+          screen-density-group="large"
+          locale-group="europe"
+          android-sdk-group="v19"
+          gl-texture-group="dxt1"
+          device-feature-group="low-latency"/>)xml");
+
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_));
+
+    EXPECT_EQ(1ul, config.artifacts.size());
+
+    auto& artifact = config.artifacts.back();
+    EXPECT_FALSE(artifact.name);  // TODO: make this fail.
+    EXPECT_EQ(1, artifact.version);
+    EXPECT_EQ("arm", artifact.abi_group.value());
+    EXPECT_EQ("large", artifact.screen_density_group.value());
+    EXPECT_EQ("europe", artifact.locale_group.value());
+    EXPECT_EQ("v19", artifact.android_sdk_group.value());
+    EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+    EXPECT_EQ("low-latency", artifact.device_feature_group.value());
+  }
+
+  {
+    // Perform a second action to ensure we get 2 artifacts.
+    const auto doc = test::BuildXmlDom(R"xml(
+      <artifact
+          abi-group="other"
+          screen-density-group="large"
+          locale-group="europe"
+          android-sdk-group="v19"
+          gl-texture-group="dxt1"
+          device-feature-group="low-latency"/>)xml");
+
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(2ul, config.artifacts.size());
+    EXPECT_EQ(2, config.artifacts.back().version);
+  }
+
+  {
+    // Perform a third action with a set version code.
+    const auto doc = test::BuildXmlDom(R"xml(
     <artifact
-        abi-group="arm"
+        version="5"
+        abi-group="other"
         screen-density-group="large"
         locale-group="europe"
         android-sdk-group="v19"
         gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml";
+        device-feature-group="low-latency"/>)xml");
 
-  auto doc = test::BuildXmlDom(xml);
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(3ul, config.artifacts.size());
+    EXPECT_EQ(5, config.artifacts.back().version);
+  }
 
-  PostProcessingConfiguration config;
-  bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_);
-  ASSERT_TRUE(ok);
-
-  EXPECT_EQ(1ul, config.artifacts.size());
-
-  auto& artifact = config.artifacts.front();
-  EXPECT_FALSE(artifact.name);  // TODO: make this fail.
-  EXPECT_EQ("arm", artifact.abi_group.value());
-  EXPECT_EQ("large", artifact.screen_density_group.value());
-  EXPECT_EQ("europe", artifact.locale_group.value());
-  EXPECT_EQ("v19", artifact.android_sdk_group.value());
-  EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
-  EXPECT_EQ("low-latency", artifact.device_feature_group.value());
-
-  // Perform a second action to ensure we get 2 artifacts.
-  static constexpr const char* second = R"xml(
+  {
+    // Perform a fourth action to ensure the version code still increments.
+    const auto doc = test::BuildXmlDom(R"xml(
     <artifact
         abi-group="other"
         screen-density-group="large"
         locale-group="europe"
         android-sdk-group="v19"
         gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml";
-  doc = test::BuildXmlDom(second);
+        device-feature-group="low-latency"/>)xml");
 
-  ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
-  ASSERT_TRUE(ok);
-  EXPECT_EQ(2ul, config.artifacts.size());
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(4ul, config.artifacts.size());
+    EXPECT_EQ(6, config.artifacts.back().version);
+  }
+}
+
+TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
+  static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
+      <pst-process xmlns="http://schemas.android.com/tools/aapt">>
+        <artifacts>
+          <artifact-format>
+            ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+          </artifact-format>
+          <artifact
+              name="art1"
+              abi-group="arm"
+              screen-density-group="large"
+              locale-group="europe"
+              android-sdk-group="v19"
+              gl-texture-group="dxt1"
+              device-feature-group="low-latency"/>
+          <artifact
+              name="art2"
+              version = "1"
+              abi-group="other"
+              screen-density-group="alldpi"
+              locale-group="north-america"
+              android-sdk-group="v19"
+              gl-texture-group="dxt1"
+              device-feature-group="low-latency"/>
+        </artifacts>
+      </post-process>)xml";
+  auto result = ConfigurationParser::ForContents(configuration).Parse();
+  ASSERT_FALSE(result);
 }
 
 TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
-  static constexpr const char* xml = R"xml(
+  const auto doc = test::BuildXmlDom(R"xml(
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
-    </artifact-format>)xml";
-
-  auto doc = test::BuildXmlDom(xml);
+    </artifact-format>)xml");
 
   PostProcessingConfiguration config;
   bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
@@ -321,9 +392,9 @@
   static constexpr const char* xml = R"xml(
     <android-sdk-group label="v19">
       <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
+          minSdkVersion="19"
+          targetSdkVersion="24"
+          maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
@@ -342,14 +413,43 @@
   auto& out = config.android_sdk_groups["v19"];
 
   AndroidSdk sdk;
-  sdk.min_sdk_version = std::string("v19");
-  sdk.target_sdk_version = std::string("v24");
-  sdk.max_sdk_version = std::string("v25");
+  sdk.min_sdk_version = 19;
+  sdk.target_sdk_version = 24;
+  sdk.max_sdk_version = 25;
   sdk.manifest = AndroidManifest();
 
   ASSERT_EQ(sdk, out);
 }
 
+TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
+  static constexpr const char* xml = R"xml(
+    <android-sdk-group label="O">
+      <android-sdk
+          minSdkVersion="M"
+          targetSdkVersion="O"
+          maxSdkVersion="O">
+      </android-sdk>
+    </android-sdk-group>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  ASSERT_EQ(1ul, config.android_sdk_groups.size());
+  ASSERT_EQ(1u, config.android_sdk_groups.count("O"));
+
+  auto& out = config.android_sdk_groups["O"];
+
+  AndroidSdk sdk;
+  sdk.min_sdk_version = {};  // Only the latest development version is supported.
+  sdk.target_sdk_version = 26;
+  sdk.max_sdk_version = 26;
+
+  ASSERT_EQ(sdk, out);
+}
+
 TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
   static constexpr const char* xml = R"xml(
     <gl-texture-group label="dxt1">
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 47bf99e..134153a 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -39,6 +39,7 @@
   <!-- Groups output artifacts together by dimension labels. -->
   <xsd:complexType name="artifact">
     <xsd:attribute name="name" type="xsd:string"/>
+    <xsd:attribute name="version" type="xsd:integer"/>
     <xsd:attribute name="abi-group" type="xsd:string"/>
     <xsd:attribute name="android-sdk-group" type="xsd:string"/>
     <xsd:attribute name="device-feature-group" type="xsd:string"/>
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 5ff8908..8e4b82c 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -22,22 +22,50 @@
 #include "androidfw/StringPiece.h"
 
 #include "LoadedApk.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
 #include "configuration/ConfigurationParser.h"
 #include "filter/AbiFilter.h"
 #include "filter/Filter.h"
 #include "flatten/Archive.h"
+#include "flatten/XmlFlattener.h"
 #include "optimize/VersionCollapser.h"
 #include "process/IResourceTableConsumer.h"
 #include "split/TableSplitter.h"
 #include "util/Files.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
 
 namespace aapt {
 
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::Artifact;
 using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::xml::kSchemaAndroid;
+using ::aapt::xml::XmlResource;
 using ::android::StringPiece;
 
+namespace {
+
+Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config,
+                                IDiagnostics* diag) {
+  if (!artifact.android_sdk_group) {
+    return {};
+  }
+
+  const std::string& group_name = artifact.android_sdk_group.value();
+  auto group = config.android_sdk_groups.find(group_name);
+  // TODO: Remove validation when configuration parser ensures referential integrity.
+  if (group == config.android_sdk_groups.end()) {
+    diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'");
+    return {};
+  }
+
+  return group->second;
+}
+
+}  // namespace
+
 /**
  * Context wrapper that allows the min Android SDK value to be overridden.
  */
@@ -127,6 +155,13 @@
       return false;
     }
 
+    std::unique_ptr<XmlResource> manifest;
+    if (!UpdateManifest(artifact, config, &manifest, diag)) {
+      diag->Error(DiagMessage() << "could not update AndroidManifest.xml for "
+                                << artifact_name.value());
+      return false;
+    }
+
     std::string out = options.out_dir;
     if (!file::mkdirs(out)) {
       context_->GetDiagnostics()->Warn(DiagMessage() << "could not create out dir: " << out);
@@ -145,7 +180,7 @@
     }
 
     if (!apk_->WriteToArchive(context_, table.get(), options.table_flattener_options, &filters,
-                              writer.get())) {
+                              writer.get(), manifest.get())) {
       return false;
     }
   }
@@ -208,37 +243,15 @@
     splits.config_filter = &axis_filter;
   }
 
-  if (artifact.android_sdk_group) {
-    const std::string& group_name = artifact.android_sdk_group.value();
-    auto group = config.android_sdk_groups.find(group_name);
-    // TODO: Remove validation when configuration parser ensures referential integrity.
-    if (group == config.android_sdk_groups.end()) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
-                                                      << group_name << "'");
-      return {};
-    }
-
-    const AndroidSdk& sdk = group->second;
-    if (!sdk.min_sdk_version) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "skipping SDK version. No min SDK: " << group_name);
-      return {};
-    }
-
-    ConfigDescription c;
-    const std::string& version = sdk.min_sdk_version.value();
-    if (!ConfigDescription::Parse(version, &c)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "could not parse min SDK: " << version);
-      return {};
-    }
-
-    wrappedContext.SetMinSdkVersion(c.sdkVersion);
+  Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context_->GetDiagnostics());
+  if (sdk && sdk.value().min_sdk_version) {
+    wrappedContext.SetMinSdkVersion(sdk.value().min_sdk_version.value());
   }
 
   std::unique_ptr<ResourceTable> table = old_table.Clone();
 
   VersionCollapser collapser;
-  if (!collapser.Consume(context_, table.get())) {
+  if (!collapser.Consume(&wrappedContext, table.get())) {
     context_->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
     return {};
   }
@@ -248,4 +261,95 @@
   return table;
 }
 
+bool MultiApkGenerator::UpdateManifest(const Artifact& artifact,
+                                       const PostProcessingConfiguration& config,
+                                       std::unique_ptr<XmlResource>* updated_manifest,
+                                       IDiagnostics* diag) {
+  *updated_manifest = apk_->InflateManifest(context_);
+  XmlResource* manifest = updated_manifest->get();
+  if (manifest == nullptr) {
+    return false;
+  }
+
+  // Make sure the first element is <manifest> with package attribute.
+  xml::Element* manifest_el = manifest->root.get();
+  if (manifest_el == nullptr) {
+    return false;
+  }
+
+  if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+    diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
+    return false;
+  }
+
+  // Update the versionCode attribute.
+  xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
+  if (versionCode == nullptr) {
+    diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
+    return false;
+  }
+
+  auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
+  if (compiled_version == nullptr) {
+    diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
+    return false;
+  }
+
+  int new_version = compiled_version->value.data + artifact.version;
+  versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
+
+  // Check to see if the minSdkVersion needs to be updated.
+  Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag);
+  if (maybe_sdk) {
+    // TODO(safarmer): Handle the rest of the Android SDK.
+    const AndroidSdk& android_sdk = maybe_sdk.value();
+
+    if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+      if (xml::Attribute* min_sdk_attr =
+              uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+        // Populate with a pre-compiles attribute to we don't need to relink etc.
+        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+        min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
+      } else {
+        // There was no minSdkVersion. This is strange since at this point we should have been
+        // through the manifest fixer which sets the default minSdkVersion.
+        diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
+        return false;
+      }
+    } else {
+      // No uses-sdk present. This is strange since at this point we should have been
+      // through the manifest fixer which should have added it.
+      diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
+      return false;
+    }
+  }
+
+  if (artifact.screen_density_group) {
+    auto densities = config.screen_density_groups.find(artifact.screen_density_group.value());
+    CHECK(densities != config.screen_density_groups.end()) << "Missing density group";
+
+    xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
+    if (!screens_el) {
+      // create a new element.
+      std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
+      new_screens_el->name = "compatible-screens";
+      screens_el = new_screens_el.get();
+      manifest_el->InsertChild(0, std::move(new_screens_el));
+    } else {
+      // clear out the old element.
+      screens_el->GetChildElements().clear();
+    }
+
+    for (const auto& density : densities->second) {
+      std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>();
+      screen_el->name = "screen";
+      const char* density_str = density.toString().string();
+      screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str});
+      screens_el->AppendChild(std::move(screen_el));
+    }
+  }
+
+  return true;
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h
index b064400..e6546ee 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.h
+++ b/tools/aapt2/optimize/MultiApkGenerator.h
@@ -54,6 +54,10 @@
     return context_->GetDiagnostics();
   }
 
+  bool UpdateManifest(const configuration::Artifact& artifact,
+                      const configuration::PostProcessingConfiguration& config,
+                      std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag);
+
   LoadedApk* apk_;
   IAaptContext* context_;
 };
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index 23f573c..e8e6adc 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -50,14 +50,6 @@
 using ::testing::Test;
 using ::testing::_;
 
-/** Subclass the LoadedApk class so that we can mock the WriteToArchive method. */
-class MockApk : public LoadedApk {
- public:
-  MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){};
-  MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&,
-                                    FilterChain*, IArchiveWriter*));
-};
-
 /**
  * Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to
  * directly test table filter.
@@ -111,54 +103,10 @@
   ConfigDescription v21_ = ParseConfigOrDie("v21");
 };
 
-TEST_F(MultiApkGeneratorTest, FromBaseApk) {
-  std::unique_ptr<ResourceTable> table = BuildTable();
-
-  MockApk apk{std::move(table)};
-
-  EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0);
-
-  test::Context ctx;
-  PostProcessingConfiguration empty_config;
-  TableFlattenerOptions table_flattener_options;
-
-  MultiApkGenerator generator{&apk, &ctx};
-  EXPECT_TRUE(generator.FromBaseApk({"out", empty_config, table_flattener_options}));
-
-  Artifact x64 = test::ArtifactBuilder()
-                     .SetName("${basename}.x64.apk")
-                     .SetAbiGroup("x64")
-                     .SetLocaleGroup("en")
-                     .SetDensityGroup("xhdpi")
-                     .Build();
-
-  Artifact intel = test::ArtifactBuilder()
-                       .SetName("${basename}.intel.apk")
-                       .SetAbiGroup("intel")
-                       .SetLocaleGroup("europe")
-                       .SetDensityGroup("large")
-                       .Build();
-
-  auto config = test::PostProcessingConfigurationBuilder()
-                    .SetLocaleGroup("en", {"en"})
-                    .SetLocaleGroup("europe", {"en", "fr", "de", "es"})
-                    .SetAbiGroup("x64", {Abi::kX86_64})
-                    .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86})
-                    .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"})
-                    .AddArtifact(x64)
-                    .AddArtifact(intel)
-                    .Build();
-
-  // Called once for each artifact.
-  EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true));
-  EXPECT_TRUE(generator.FromBaseApk({"out", config, table_flattener_options}));
-}
-
 TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -174,7 +122,7 @@
                     .SetLocaleGroup("en", {"en"})
                     .SetAbiGroup("x64", {Abi::kX86_64})
                     .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v23", AndroidSdk::ForMinSdk("v23"))
+                    .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23))
                     .AddArtifact(x64)
                     .Build();
 
@@ -199,7 +147,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -215,7 +163,7 @@
                     .SetLocaleGroup("en", {"en"})
                     .SetAbiGroup("x64", {Abi::kX86_64})
                     .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v4", AndroidSdk::ForMinSdk("v4"))
+                    .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4))
                     .AddArtifact(x64)
                     .Build();
 
@@ -238,7 +186,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
new file mode 100644
index 0000000..fb1636f
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.net.wifi.rtt.RangingResult;
+
+/**
+ * Interface for RTT result callback.
+ *
+ * @hide
+ */
+oneway interface IRttCallback
+{
+    /**
+     * Service to manager callback providing RTT status and results.
+     */
+    void onRangingResults(int status, in List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
new file mode 100644
index 0000000..ad92e04
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.RangingRequest;
+
+/**
+ * @hide
+ */
+interface IWifiRttManager
+{
+    void startRanging(in IBinder binder, in String callingPackage, in RangingRequest request,
+            in IRttCallback callback);
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.aidl b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
new file mode 100644
index 0000000..8053c94
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+parcelable RangingRequest;
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 0000000..997b680
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
+ * <p>
+ * The ranging request is a batch request - specifying a set of devices (specified using
+ * {@link RangingRequest.Builder#addAp(ScanResult)} and
+ * {@link RangingRequest.Builder#addAps(List)}).
+ *
+ * @hide RTT_API
+ */
+public final class RangingRequest implements Parcelable {
+    private static final int MAX_PEERS = 10;
+
+    /**
+     * Returns the maximum number of peers to range which can be specified in a single {@code
+     * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
+     * through {@link RangingRequest.Builder#addAp(ScanResult)} or
+     * {@link RangingRequest.Builder#addAps(List)}.
+     *
+     * @return Maximum number of peers.
+     */
+    public static int getMaxPeers() {
+        return MAX_PEERS;
+    }
+
+    /** @hide */
+    public final List<RttPeer> mRttPeers;
+
+    /** @hide */
+    private RangingRequest(List<RttPeer> rttPeers) {
+        mRttPeers = rttPeers;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeList(mRttPeers);
+    }
+
+    public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+        @Override
+        public RangingRequest[] newArray(int size) {
+            return new RangingRequest[size];
+        }
+
+        @Override
+        public RangingRequest createFromParcel(Parcel in) {
+            return new RangingRequest(in.readArrayList(null));
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString() {
+        StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ",");
+        for (RttPeer rp : mRttPeers) {
+            sj.add(rp.toString());
+        }
+        return sj.toString();
+    }
+
+    /** @hide */
+    public void enforceValidity() {
+        if (mRttPeers.size() > MAX_PEERS) {
+            throw new IllegalArgumentException(
+                    "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+        }
+    }
+
+    /**
+     * Builder class used to construct {@link RangingRequest} objects.
+     */
+    public static final class Builder {
+        private List<RttPeer> mRttPeers = new ArrayList<>();
+
+        /**
+         * Add the device specified by the {@link ScanResult} to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAp(ScanResult apInfo) {
+            if (apInfo == null) {
+                throw new IllegalArgumentException("Null ScanResult!");
+            }
+            mRttPeers.add(new RttPeerAp(apInfo));
+            return this;
+        }
+
+        /**
+         * Add the devices specified by the {@link ScanResult}s to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAps(List<ScanResult> apInfos) {
+            if (apInfos == null) {
+                throw new IllegalArgumentException("Null list of ScanResults!");
+            }
+            for (ScanResult scanResult : apInfos) {
+                addAp(scanResult);
+            }
+            return this;
+        }
+
+        /**
+         * Build {@link RangingRequest} given the current configurations made on the
+         * builder.
+         */
+        public RangingRequest build() {
+            return new RangingRequest(mRttPeers);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingRequest)) {
+            return false;
+        }
+
+        RangingRequest lhs = (RangingRequest) o;
+
+        return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+    }
+
+    @Override
+    public int hashCode() {
+        return mRttPeers.hashCode();
+    }
+
+    /** @hide */
+    public interface RttPeer {
+        // empty (marker interface)
+    }
+
+    /** @hide */
+    public static class RttPeerAp implements RttPeer, Parcelable {
+        public final ScanResult scanResult;
+
+        public RttPeerAp(ScanResult scanResult) {
+            this.scanResult = scanResult;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            scanResult.writeToParcel(dest, flags);
+        }
+
+        public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() {
+            @Override
+            public RttPeerAp[] newArray(int size) {
+                return new RttPeerAp[size];
+            }
+
+            @Override
+            public RttPeerAp createFromParcel(Parcel in) {
+                return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in));
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAp: scanResult=").append(
+                    scanResult.toString()).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAp)) {
+                return false;
+            }
+
+            RttPeerAp lhs = (RttPeerAp) o;
+
+            // Note: the only thing which matters for the request identity is the BSSID of the AP
+            return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID);
+        }
+
+        @Override
+        public int hashCode() {
+            return scanResult.hashCode();
+        }
+    }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.aidl b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
new file mode 100644
index 0000000..ae295a6
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+parcelable RangingResult;
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 0000000..918803e
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are
+ * returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ *
+ * @hide RTT_API
+ */
+public final class RangingResult implements Parcelable {
+    private static final String TAG = "RangingResult";
+
+    private final int mStatus;
+    private final byte[] mMac;
+    private final int mDistanceCm;
+    private final int mDistanceStdDevCm;
+    private final int mRssi;
+    private final long mTimestamp;
+
+    /** @hide */
+    public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
+            long timestamp) {
+        mStatus = status;
+        mMac = mac;
+        mDistanceCm = distanceCm;
+        mDistanceStdDevCm = distanceStdDevCm;
+        mRssi = rssi;
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
+     * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * @return The MAC address of the device whose range measurement was requested. Will correspond
+     * to the MAC address of the device in the {@link RangingRequest}.
+     * <p>
+     * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+     */
+    public byte[] getMacAddress() {
+        return mMac;
+    }
+
+    /**
+     * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public int getDistanceCm() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            Log.e(TAG, "getDistanceCm(): invalid value retrieved");
+        }
+        return mDistanceCm;
+    }
+
+    /**
+     * @return The standard deviation of the measured distance (in cm) to the device specified by
+     * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
+     * executed in a single RTT burst.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public int getDistanceStdDevCm() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+        }
+        return mDistanceStdDevCm;
+    }
+
+    /**
+     * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public int getRssi() {
+        if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+            // TODO: should this be an exception?
+            Log.e(TAG, "getRssi(): invalid value retrieved");
+        }
+        return mRssi;
+    }
+
+    /**
+     * @return The timestamp (in us) at which the ranging operation was performed
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+     */
+    public long getRangingTimestamp() {
+        return mTimestamp;
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStatus);
+        dest.writeByteArray(mMac);
+        dest.writeInt(mDistanceCm);
+        dest.writeInt(mDistanceStdDevCm);
+        dest.writeInt(mRssi);
+        dest.writeLong(mTimestamp);
+    }
+
+    /** @hide */
+    public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+        @Override
+        public RangingResult[] newArray(int size) {
+            return new RangingResult[size];
+        }
+
+        @Override
+        public RangingResult createFromParcel(Parcel in) {
+            int status = in.readInt();
+            byte[] mac = in.createByteArray();
+            int distanceCm = in.readInt();
+            int distanceStdDevCm = in.readInt();
+            int rssi = in.readInt();
+            long timestamp = in.readLong();
+            return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString() {
+        return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
+                mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
+                ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
+                mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+                mTimestamp).append("]").toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingResult)) {
+            return false;
+        }
+
+        RangingResult lhs = (RangingResult) o;
+
+        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
+                && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
+                && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+    }
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 0000000..d7270ad
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.os.Handler;
+
+import java.util.List;
+
+/**
+ * Base class for ranging result callbacks. Should be extended by applications and set when calling
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
+ * result from a range request will be called in this object.
+ *
+ * @hide RTT_API
+ */
+public abstract class RangingResultCallback {
+    /**
+     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+     * operation was successful and distance value is valid.
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+     * operation failed and the distance value is invalid.
+     */
+    public static final int STATUS_FAIL = 1;
+
+    /**
+     * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
+     * devices specified in the request was attempted.
+     */
+    public abstract void onRangingFailure();
+
+    /**
+     * Called when a ranging operation was executed. The list of results corresponds to devices
+     * specified in the ranging request.
+     *
+     * @param results List of range measurements, one per requested device.
+     */
+    public abstract void onRangingResults(List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 0000000..a085de1
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,100 @@
+package android.net.wifi.rtt;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <p>
+ * Ranging requests are triggered using
+ * {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
+ * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
+ * callback.
+ *
+ * @hide RTT_API
+ */
+@SystemService(Context.WIFI_RTT2_SERVICE)
+public class WifiRttManager {
+    private static final String TAG = "WifiRttManager";
+    private static final boolean VDBG = true;
+
+    private final Context mContext;
+    private final IWifiRttManager mService;
+
+    /** @hide */
+    public WifiRttManager(Context context, IWifiRttManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+     * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+     *
+     * @param request  A request specifying a set of devices whose distance measurements are
+     *                 requested.
+     * @param callback A callback for the result of the ranging request.
+     * @param handler  The Handler on whose thread to execute the callbacks of the {@code
+     *                 callback} object. If a null is provided then the application's main thread
+     *                 will be used.
+     */
+    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+    public void startRanging(RangingRequest request, RangingResultCallback callback,
+            @Nullable Handler handler) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
+                    + handler);
+        }
+
+        Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+        Binder binder = new Binder();
+        try {
+            mService.startRanging(binder, mContext.getOpPackageName(), request,
+                    new RttCallbackProxy(looper, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class RttCallbackProxy extends IRttCallback.Stub {
+        private final Handler mHandler;
+        private final RangingResultCallback mCallback;
+
+        RttCallbackProxy(Looper looper, RangingResultCallback callback) {
+            mHandler = new Handler(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
+            if (VDBG) {
+                Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
+                        + results);
+            }
+            mHandler.post(() -> {
+               if (status == RangingResultCallback.STATUS_SUCCESS) {
+                   mCallback.onRangingResults(results);
+               } else {
+                   mCallback.onRangingFailure();
+               }
+            });
+        }
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
new file mode 100644
index 0000000..23c75ce
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test harness for WifiRttManager class.
+ */
+@SmallTest
+public class WifiRttManagerTest {
+    private WifiRttManager mDut;
+    private TestLooper mMockLooper;
+    private Handler mMockLooperHandler;
+
+    private final String packageName = "some.package.name.for.rtt.app";
+
+    @Mock
+    public Context mockContext;
+
+    @Mock
+    public IWifiRttManager mockRttService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiRttManager(mockContext, mockRttService);
+        mMockLooper = new TestLooper();
+        mMockLooperHandler = new Handler(mMockLooper.getLooper());
+
+        when(mockContext.getOpPackageName()).thenReturn(packageName);
+    }
+
+    /**
+     * Validate ranging call flow with succesful results.
+     */
+    @Test
+    public void testRangeSuccess() throws Exception {
+        RangingRequest request = new RangingRequest.Builder().build();
+        List<RangingResult> results = new ArrayList<>();
+        results.add(new RangingResult(RangingResultCallback.STATUS_SUCCESS, null, 15, 5, 10, 666));
+        RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+        ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+        // verify ranging request passed to service
+        mDut.startRanging(request, callbackMock, mMockLooperHandler);
+        verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+                callbackCaptor.capture());
+
+        // service calls back with success
+        callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_SUCCESS, results);
+        mMockLooper.dispatchAll();
+        verify(callbackMock).onRangingResults(results);
+
+        verifyNoMoreInteractions(mockRttService, callbackMock);
+    }
+
+    /**
+     * Validate ranging call flow which failed.
+     */
+    @Test
+    public void testRangeFail() throws Exception {
+        RangingRequest request = new RangingRequest.Builder().build();
+        RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+        ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+        // verify ranging request passed to service
+        mDut.startRanging(request, callbackMock, mMockLooperHandler);
+        verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+                callbackCaptor.capture());
+
+        // service calls back with failure code
+        callbackCaptor.getValue().onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+        mMockLooper.dispatchAll();
+        verify(callbackMock).onRangingFailure();
+
+        verifyNoMoreInteractions(mockRttService, callbackMock);
+    }
+
+    /**
+     * Validate that RangingRequest parcel works (produces same object on write/read).
+     */
+    @Test
+    public void testRangingRequestParcel() {
+        // Note: not validating parcel code of ScanResult (assumed to work)
+        ScanResult scanResult1 = new ScanResult();
+        scanResult1.BSSID = "00:01:02:03:04:05";
+        ScanResult scanResult2 = new ScanResult();
+        scanResult2.BSSID = "06:07:08:09:0A:0B";
+        ScanResult scanResult3 = new ScanResult();
+        scanResult3.BSSID = "AA:BB:CC:DD:EE:FF";
+        List<ScanResult> scanResults2and3 = new ArrayList<>(2);
+        scanResults2and3.add(scanResult2);
+        scanResults2and3.add(scanResult3);
+
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAp(scanResult1);
+        builder.addAps(scanResults2and3);
+        RangingRequest request = builder.build();
+
+        Parcel parcelW = Parcel.obtain();
+        request.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        RangingRequest rereadRequest = RangingRequest.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(request, rereadRequest);
+    }
+
+    /**
+     * Validate that can request as many range operation as the upper limit on number of requests.
+     */
+    @Test
+    public void testRangingRequestAtLimit() {
+        ScanResult scanResult = new ScanResult();
+        List<ScanResult> scanResultList = new ArrayList<>();
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
+            scanResultList.add(scanResult);
+        }
+
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAp(scanResult);
+        builder.addAps(scanResultList);
+        builder.addAp(scanResult);
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity();
+    }
+
+    /**
+     * Validate that limit on number of requests is applied.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangingRequestPastLimit() {
+        ScanResult scanResult = new ScanResult();
+        List<ScanResult> scanResultList = new ArrayList<>();
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 1; ++i) {
+            scanResultList.add(scanResult);
+        }
+
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAp(scanResult);
+        builder.addAps(scanResultList);
+        builder.addAp(scanResult);
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity();
+    }
+
+    /**
+     * Validate that RangingResults parcel works (produces same object on write/read).
+     */
+    @Test
+    public void testRangingResultsParcel() {
+        // Note: not validating parcel code of ScanResult (assumed to work)
+        int status = RangingResultCallback.STATUS_SUCCESS;
+        final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+        int distanceCm = 105;
+        int distanceStdDevCm = 10;
+        int rssi = 5;
+        long timestamp = System.currentTimeMillis();
+
+        RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
+                timestamp);
+
+        Parcel parcelW = Parcel.obtain();
+        result.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(result, rereadResult);
+    }
+}