Merge "[Magnifier - 1] Initial implementation and wiring"
diff --git a/Android.bp b/Android.bp
index 43fb48a..80f1c37 100644
--- a/Android.bp
+++ b/Android.bp
@@ -65,3 +65,25 @@
 optional_subdirs = [
     "core/tests/utiltests/jni",
 ]
+
+java_library {
+    name: "hwbinder",
+    no_framework_libs: true,
+
+    srcs: [
+        "core/java/android/os/HidlSupport.java",
+        "core/java/android/annotation/NonNull.java",
+        "core/java/android/os/HwBinder.java",
+        "core/java/android/os/HwBlob.java",
+        "core/java/android/os/HwParcel.java",
+        "core/java/android/os/IHwBinder.java",
+        "core/java/android/os/IHwInterface.java",
+        "core/java/android/os/DeadObjectException.java",
+        "core/java/android/os/DeadSystemException.java",
+        "core/java/android/os/RemoteException.java",
+        "core/java/android/util/AndroidException.java",
+    ],
+
+    dxflags: ["--core-library"],
+    installable: false,
+}
diff --git a/Android.mk b/Android.mk
index aa7caa5..98e4299 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 \
@@ -650,32 +652,6 @@
 
 framework_built := $(call java-lib-deps,framework)
 
-# HwBinder
-# =======================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-        core/java/android/os/HidlSupport.java \
-        core/java/android/annotation/NonNull.java \
-        core/java/android/os/HwBinder.java \
-        core/java/android/os/HwBlob.java \
-        core/java/android/os/HwParcel.java \
-        core/java/android/os/IHwBinder.java \
-        core/java/android/os/IHwInterface.java \
-        core/java/android/os/DeadObjectException.java \
-        core/java/android/os/DeadSystemException.java \
-        core/java/android/os/RemoteException.java \
-        core/java/android/util/AndroidException.java \
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := hwbinder
-
-LOCAL_DX_FLAGS := --core-library
-LOCAL_UNINSTALLABLE_MODULE := true
-include $(BUILD_JAVA_LIBRARY)
-
 # Copy AIDL files to be preprocessed and included in the SDK,
 # specified relative to the root of the build tree.
 # ============================================================
@@ -721,6 +697,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 bc77019..58523f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6683,6 +6683,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -40690,8 +40691,13 @@
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -40765,6 +40771,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();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0e082b1..4396dc4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6933,6 +6933,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -40890,17 +40891,22 @@
 
   public final class Suggestion implements android.os.Parcelable {
     method public int describeContents();
+    method public int getFlags();
+    method public android.graphics.drawable.Icon getIcon();
     method public java.lang.String getId();
     method public android.app.PendingIntent getPendingIntent();
     method public java.lang.CharSequence getSummary();
     method public java.lang.CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.settings.suggestions.Suggestion> CREATOR;
+    field public static final int FLAG_HAS_BUTTON = 1; // 0x1
   }
 
   public static class Suggestion.Builder {
     ctor public Suggestion.Builder(java.lang.String);
     method public android.service.settings.suggestions.Suggestion build();
+    method public android.service.settings.suggestions.Suggestion.Builder setFlags(int);
+    method public android.service.settings.suggestions.Suggestion.Builder setIcon(android.graphics.drawable.Icon);
     method public android.service.settings.suggestions.Suggestion.Builder setPendingIntent(android.app.PendingIntent);
     method public android.service.settings.suggestions.Suggestion.Builder setSummary(java.lang.CharSequence);
     method public android.service.settings.suggestions.Suggestion.Builder setTitle(java.lang.CharSequence);
@@ -44287,8 +44293,13 @@
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -44371,6 +44382,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();
diff --git a/api/test-current.txt b/api/test-current.txt
index 6192e4b..935219f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6754,6 +6754,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -41040,8 +41041,13 @@
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -41115,6 +41121,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();
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/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/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 163a8dc..64b9ae8 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -25,6 +25,7 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -135,12 +136,15 @@
     private boolean mLights;
     private int mLightColor = DEFAULT_LIGHT_COLOR;
     private long[] mVibration;
+    // Bitwise representation of fields that have been changed by the user, preventing the app from
+    // making changes to these fields.
     private int mUserLockedFields;
     private boolean mVibrationEnabled;
     private boolean mShowBadge = DEFAULT_SHOW_BADGE;
     private boolean mDeleted = DEFAULT_DELETED;
     private String mGroup;
     private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+    // If this is a blockable system notification channel.
     private boolean mBlockableSystem = false;
 
     /**
@@ -850,4 +854,35 @@
                 + ", mBlockableSystem=" + mBlockableSystem
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelProto.ID, mId);
+        proto.write(NotificationChannelProto.NAME, mName);
+        proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
+        proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
+        proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
+        proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
+        if (mSound != null) {
+            proto.write(NotificationChannelProto.SOUND, mSound.toString());
+        }
+        proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
+        proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
+        if (mVibration != null) {
+            for (long v : mVibration) {
+                proto.write(NotificationChannelProto.VIBRATION, v);
+            }
+        }
+        proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
+        proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
+        proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
+        proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
+        proto.write(NotificationChannelProto.GROUP, mGroup);
+        if (mAudioAttributes != null) {
+            long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
+            mAudioAttributes.toProto(proto);
+            proto.end(aToken);
+        }
+        proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+    }
 }
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 5173311..5cb7fb7 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -295,4 +296,15 @@
                 + ", mChannels=" + mChannels
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelGroupProto.ID, mId);
+        proto.write(NotificationChannelGroupProto.NAME, mName.toString());
+        proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
+        proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
+        for (NotificationChannel channel : mChannels) {
+            channel.toProto(proto);
+        }
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ab70f0e..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/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index c208f1d..d9b7cd7 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -674,6 +674,7 @@
 
         ViewNodeText mText;
         int mInputType;
+        String mWebScheme;
         String mWebDomain;
         Bundle mExtras;
         LocaleList mLocaleList;
@@ -751,6 +752,7 @@
                 mInputType = in.readInt();
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                mWebScheme = in.readString();
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -813,7 +815,7 @@
             if (mInputType != 0) {
                 flags |= FLAGS_HAS_INPUT_TYPE;
             }
-            if (mWebDomain != null) {
+            if (mWebScheme != null || mWebDomain != null) {
                 flags |= FLAGS_HAS_URL;
             }
             if (mLocaleList != null) {
@@ -908,6 +910,7 @@
                 out.writeInt(mInputType);
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                out.writeString(mWebScheme);
                 out.writeString(mWebDomain);
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -1265,13 +1268,26 @@
          * {@link android.service.autofill.AutofillService} for more details.
          *
          * @return domain-only part of the document. For example, if the full URL is
-         * {@code https://my.site/login?user=my_user}, it returns {@code my.site}.
+         * {@code https://example.com/login?user=my_user}, it returns {@code example.com}.
          */
         @Nullable public String getWebDomain() {
             return mWebDomain;
         }
 
         /**
+         * Returns the scheme of the HTML document represented by this view.
+         *
+         * <p>Typically used when the view associated with the view is a container for an HTML
+         * document.
+         *
+         * @return scheme-only part of the document. For example, if the full URL is
+         * {@code https://example.com/login?user=my_user}, it returns {@code https}.
+         */
+        @Nullable public String getWebScheme() {
+            return mWebScheme;
+        }
+
+        /**
          * Returns the HTML properties associated with this view.
          *
          * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
@@ -1767,10 +1783,13 @@
         @Override
         public void setWebDomain(@Nullable String domain) {
             if (domain == null) {
+                mNode.mWebScheme = null;
                 mNode.mWebDomain = null;
                 return;
             }
-            mNode.mWebDomain = Uri.parse(domain).getHost();
+            Uri uri = Uri.parse(domain);
+            mNode.mWebScheme = uri.getScheme();
+            mNode.mWebDomain = uri.getHost();
         }
 
         @Override
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 87e516c..1434c9b 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -317,7 +317,8 @@
     }
 
     /**
-     * Whether this job needs the device to be plugged in.
+     * Whether this job requires that the device be charging (or be a non-battery-powered
+     * device connected to permanent power, such as Android TV devices).
      */
     public boolean isRequireCharging() {
         return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
@@ -331,7 +332,10 @@
     }
 
     /**
-     * Whether this job needs the device to be in an Idle maintenance window.
+     * Whether this job requires that the user <em>not</em> be interacting with the device.
+     *
+     * <p class="note">This is <em>not</em> the same as "doze" or "device idle";
+     * it is purely about the user's direct interactions.</p>
      */
     public boolean isRequireDeviceIdle() {
         return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
@@ -918,9 +922,19 @@
         }
 
         /**
-         * Specify that to run this job, the device needs to be plugged in. This defaults to
-         * false.
-         * @param requiresCharging Whether or not the device is plugged in.
+         * Specify that to run this job, the device must be charging (or be a
+         * non-battery-powered device connected to permanent power, such as Android TV
+         * devices). This defaults to {@code false}.
+         *
+         * <p class="note">For purposes of running jobs, a battery-powered device
+         * "charging" is not quite the same as simply being connected to power.  If the
+         * device is so busy that the battery is draining despite a power connection, jobs
+         * with this constraint will <em>not</em> run.  This can happen during some
+         * common use cases such as video chat, particularly if the device is plugged in
+         * to USB rather than to wall power.
+         *
+         * @param requiresCharging Pass {@code true} to require that the device be
+         *     charging in order to run the job.
          */
         public Builder setRequiresCharging(boolean requiresCharging) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
@@ -942,14 +956,22 @@
         }
 
         /**
-         * Specify that to run, the job needs the device to be in idle mode. This defaults to
-         * false.
-         * <p>Idle mode is a loose definition provided by the system, which means that the device
-         * is not in use, and has not been in use for some time. As such, it is a good time to
-         * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
-         * to your application, and surfaced to the user in battery stats.</p>
-         * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
-         *                           window.
+         * When set {@code true}, ensure that this job will not run if the device is in active use.
+         * The default state is {@code false}: that is, the for the job to be runnable even when
+         * someone is interacting with the device.
+         *
+         * <p>This state is a loose definition provided by the system. In general, it means that
+         * the device is not currently being used interactively, and has not been in use for some
+         * time. As such, it is a good time to perform resource heavy jobs. Bear in mind that
+         * battery usage will still be attributed to your application, and surfaced to the user in
+         * battery stats.</p>
+         *
+         * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+         * related to the system's "device idle" or "doze" states.  This constraint only
+         * determines whether a job is allowed to run while the device is directly in use.
+         *
+         * @param requiresDeviceIdle Pass {@code true} to prevent the job from running
+         *     while the device is being used interactively.
          */
         public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ef8f84b..31ca198 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2330,6 +2330,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Wi-Fi RTT (IEEE 802.11mc).
+     *
+     * @hide RTT_API
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports LoWPAN networking.
      * @hide
      */
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3726f47..7f4dee6 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1412,7 +1412,11 @@
          */
         public void release(int flags) {
             synchronized (mToken) {
-                mInternalCount--;
+                if (mInternalCount > 0) {
+                    // internal count must only be decreased if it is > 0 or state of
+                    // the WakeLock object is broken.
+                    mInternalCount--;
+                }
                 if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
                     mExternalCount--;
                 }
diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index f27cc2e..cfeb7fc 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -16,12 +16,17 @@
 
 package android.service.settings.suggestions;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Data object that has information about a device suggestion.
  *
@@ -30,9 +35,27 @@
 @SystemApi
 public final class Suggestion implements Parcelable {
 
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+            FLAG_HAS_BUTTON,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {
+    }
+
+    /**
+     * Flag for suggestion type with a single button
+     */
+    public static final int FLAG_HAS_BUTTON = 1 << 0;
+
     private final String mId;
     private final CharSequence mTitle;
     private final CharSequence mSummary;
+    private final Icon mIcon;
+    @Flags
+    private final int mFlags;
     private final PendingIntent mPendingIntent;
 
     /**
@@ -57,6 +80,22 @@
     }
 
     /**
+     * Optional icon for this suggestion.
+     */
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Optional flags for this suggestion. This will influence UI when rendering suggestion in
+     * different style.
+     */
+    @Flags
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * The Intent to launch when the suggestion is activated.
      */
     public PendingIntent getPendingIntent() {
@@ -67,6 +106,8 @@
         mId = builder.mId;
         mTitle = builder.mTitle;
         mSummary = builder.mSummary;
+        mIcon = builder.mIcon;
+        mFlags = builder.mFlags;
         mPendingIntent = builder.mPendingIntent;
     }
 
@@ -74,6 +115,8 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
+        mIcon = in.readParcelable(Icon.class.getClassLoader());
+        mFlags = in.readInt();
         mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
     }
 
@@ -99,6 +142,8 @@
         dest.writeString(mId);
         dest.writeCharSequence(mTitle);
         dest.writeCharSequence(mSummary);
+        dest.writeParcelable(mIcon, flags);
+        dest.writeInt(mFlags);
         dest.writeParcelable(mPendingIntent, flags);
     }
 
@@ -109,6 +154,9 @@
         private final String mId;
         private CharSequence mTitle;
         private CharSequence mSummary;
+        private Icon mIcon;
+        @Flags
+        private int mFlags;
         private PendingIntent mPendingIntent;
 
         public Builder(String id) {
@@ -135,6 +183,23 @@
         }
 
         /**
+         * Sets icon for the suggestion.
+         */
+        public Builder setIcon(Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets a UI type for this suggestion. This will influence UI when rendering suggestion in
+         * different style.
+         */
+        public Builder setFlags(@Flags int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
          * Sets suggestion intent
          */
         public Builder setPendingIntent(PendingIntent pendingIntent) {
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 6e69c8f..b09ccc2 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -156,6 +156,14 @@
         }
     }
 
+    /**
+     * Apply the style.
+     *
+     * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
+     * text width.
+     * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
+     * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+     */
     float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
         if (fm != null) {
             paint.getFontMetricsInt(fm);
@@ -169,7 +177,8 @@
             if (mBuilder == null) {
                 return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
             } else {
-                return mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                return 0.0f;  // Builder.addStyleRun doesn't return the width.
             }
         }
 
@@ -182,7 +191,8 @@
                     totalAdvance +=
                             paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 } else {
-                    totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
+                    // Builder.addStyleRun doesn't return the width.
+                    mBuilder.addStyleRun(paint, q, i, isRtl);
                 }
                 if (i == e) {
                     break;
@@ -191,7 +201,7 @@
                 level = mLevels[i];
             }
         }
-        return totalAdvance;
+        return totalAdvance;  // If mBuilder is null, the result is zero.
     }
 
     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index e2c31de..961cd8ee 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -453,10 +453,10 @@
             }
         }
 
-        /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
+        /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
             Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
-            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl,
-                    locHyph.first, locHyph.second);
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
+                    locHyph.second);
         }
 
         /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
@@ -1541,7 +1541,7 @@
             @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
             @IntRange(from = 0) int indentsOffset);
 
-    private static native float nAddStyleRun(
+    private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
             @Nullable String languageTags, @Nullable long[] hyphenators);
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 9afa56d..49f8eea 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -2375,6 +2375,9 @@
         if (countString == null) {
             countString = "fieldCount=" + fieldCount;
         }
+        if (countString.length() > 0) {
+            countString += " ";
+        }
 
         final long fieldType = fieldId & FIELD_TYPE_MASK;
         String typeString = getFieldTypeString(fieldType);
@@ -2382,7 +2385,7 @@
             typeString = "fieldType=" + fieldType;
         }
 
-        return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+        return countString + typeString + " tag=" + ((int) fieldId)
                 + " fieldId=0x" + Long.toHexString(fieldId);
     }
 
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
new file mode 100644
index 0000000..449baca
--- /dev/null
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util.proto;
+
+import android.util.AggStats;
+
+/**
+ * This class contains a list of helper functions to write common proto in
+ * //frameworks/base/core/proto/android/base directory
+ */
+public class ProtoUtils {
+
+    /**
+     * Dump AggStats to ProtoOutputStream
+     * @hide
+     */
+    public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+            long min, long average, long max) {
+        final long aggStatsToken = proto.start(fieldId);
+        proto.write(AggStats.MIN, min);
+        proto.write(AggStats.AVERAGE, average);
+        proto.write(AggStats.MAX, max);
+        proto.end(aggStatsToken);
+    }
+}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 0ecd20d..f671c34 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -378,7 +378,7 @@
      *
      * <p>Typically used when the view is a container for an HTML document.
      *
-     * @param domain URL representing the domain; only the host part will be used.
+     * @param domain RFC 2396-compliant URI representing the domain.
      */
     public abstract void setWebDomain(@Nullable String domain);
 
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 077a901..dfc81b2 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2972,8 +2972,12 @@
             return webviewPackage;
         }
 
+        IWebViewUpdateService service = WebViewFactory.getUpdateService();
+        if (service == null) {
+            return null;
+        }
         try {
-            return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
+            return service.getCurrentWebViewPackage();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 994512f..36b24ff 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -51,9 +51,6 @@
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
-    private static final String NULL_WEBVIEW_FACTORY =
-            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
-
     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
             "persist.sys.webview.vmsize";
 
@@ -66,6 +63,7 @@
     private static WebViewFactoryProvider sProviderInstance;
     private static final Object sProviderLock = new Object();
     private static PackageInfo sPackageInfo;
+    private static Boolean sWebViewSupported;
 
     // Error codes for loadWebViewNativeLibraryFromPackage
     public static final int LIBLOAD_SUCCESS = 0;
@@ -105,6 +103,16 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
+    private static boolean isWebViewSupported() {
+        // No lock; this is a benign race as Boolean's state is final and the PackageManager call
+        // will always return the same value.
+        if (sWebViewSupported == null) {
+            sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
+        }
+        return sWebViewSupported;
+    }
+
     /**
      * @hide
      */
@@ -135,6 +143,10 @@
      */
     public static int loadWebViewNativeLibraryFromPackage(String packageName,
                                                           ClassLoader clazzLoader) {
+        if (!isWebViewSupported()) {
+            return LIBLOAD_WRONG_PACKAGE_NAME;
+        }
+
         WebViewProviderResponse response = null;
         try {
             response = getUpdateService().waitForAndGetProvider();
@@ -188,6 +200,11 @@
                         "For security reasons, WebView is not allowed in privileged processes");
             }
 
+            if (!isWebViewSupported()) {
+                // Device doesn't support WebView; don't try to load it, just throw.
+                throw new UnsupportedOperationException();
+            }
+
             StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
@@ -410,15 +427,6 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (MissingWebViewPackageException e) {
-            // If the package doesn't exist, then try loading the null WebView instead.
-            // If that succeeds, then this is a device without WebView support; if it fails then
-            // swallow the failure, complain that the real WebView is missing and rethrow the
-            // original exception.
-            try {
-                return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
-            } catch (ClassNotFoundException e2) {
-                // Ignore.
-            }
             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
             throw new AndroidRuntimeException(e);
         }
@@ -483,7 +491,11 @@
 
     /** @hide */
     public static IWebViewUpdateService getUpdateService() {
-        return IWebViewUpdateService.Stub.asInterface(
-                ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
+        if (isWebViewSupported()) {
+            return IWebViewUpdateService.Stub.asInterface(
+                    ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
+        } else {
+            return null;
+        }
     }
 }
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 2f7d685..629891c 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -31,8 +31,12 @@
      * Fetch all packages that could potentially implement WebView.
      */
     public static WebViewProviderInfo[] getAllWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getAllWebViewPackages();
+            return service.getAllWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -42,8 +46,12 @@
      * Fetch all packages that could potentially implement WebView and are currently valid.
      */
     public static WebViewProviderInfo[] getValidWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getValidWebViewPackages();
+            return service.getValidWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -53,8 +61,12 @@
      * Used by DevelopmentSetting to get the name of the WebView provider currently in use.
      */
     public static String getCurrentWebViewPackageName() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return null;
+        }
         try {
-            return getUpdateService().getCurrentWebViewPackageName();
+            return service.getCurrentWebViewPackageName();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index afa9610..8003575 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -267,7 +267,7 @@
     SuggestionRangeSpan mSuggestionRangeSpan;
     private Runnable mShowSuggestionRunnable;
 
-    Drawable mCursorDrawable = null;
+    Drawable mDrawableForCursor = null;
 
     private Drawable mSelectHandleLeft;
     private Drawable mSelectHandleRight;
@@ -1697,7 +1697,7 @@
             mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
         }
 
-        if (highlight != null && selectionStart == selectionEnd && mCursorDrawable != null) {
+        if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
             drawCursor(canvas, cursorOffsetVertical);
             // Rely on the drawable entirely, do not draw the cursor line.
             // Has to be done after the IMM related code above which relies on the highlight.
@@ -1892,8 +1892,8 @@
     private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
         final boolean translate = cursorOffsetVertical != 0;
         if (translate) canvas.translate(0, cursorOffsetVertical);
-        if (mCursorDrawable != null) {
-            mCursorDrawable.draw(canvas);
+        if (mDrawableForCursor != null) {
+            mDrawableForCursor.draw(canvas);
         }
         if (translate) canvas.translate(0, -cursorOffsetVertical);
     }
@@ -1952,7 +1952,7 @@
 
     void updateCursorPosition() {
         if (mTextView.mCursorDrawableRes == 0) {
-            mCursorDrawable = null;
+            mDrawableForCursor = null;
             return;
         }
 
@@ -2333,17 +2333,17 @@
     @VisibleForTesting
     @Nullable
     public Drawable getCursorDrawable() {
-        return mCursorDrawable;
+        return mDrawableForCursor;
     }
 
     private void updateCursorPosition(int top, int bottom, float horizontal) {
-        if (mCursorDrawable == null) {
-            mCursorDrawable = mTextView.getContext().getDrawable(
+        if (mDrawableForCursor == null) {
+            mDrawableForCursor = mTextView.getContext().getDrawable(
                     mTextView.mCursorDrawableRes);
         }
-        final int left = clampHorizontalPosition(mCursorDrawable, horizontal);
-        final int width = mCursorDrawable.getIntrinsicWidth();
-        mCursorDrawable.setBounds(left, top - mTempRect.top, left + width,
+        final int left = clampHorizontalPosition(mDrawableForCursor, horizontal);
+        final int width = mDrawableForCursor.getIntrinsicWidth();
+        mDrawableForCursor.setBounds(left, top - mTempRect.top, left + width,
                 bottom + mTempRect.bottom);
     }
 
@@ -4712,9 +4712,9 @@
         @Override
         protected int getCursorOffset() {
             int offset = super.getCursorOffset();
-            if (mCursorDrawable != null) {
-                mCursorDrawable.getPadding(mTempRect);
-                offset += (mCursorDrawable.getIntrinsicWidth()
+            if (mDrawableForCursor != null) {
+                mDrawableForCursor.getPadding(mTempRect);
+                offset += (mDrawableForCursor.getIntrinsicWidth()
                            - mTempRect.left - mTempRect.right) / 2;
             }
             return offset;
@@ -4722,9 +4722,9 @@
 
         @Override
         int getCursorHorizontalPosition(Layout layout, int offset) {
-            if (mCursorDrawable != null) {
+            if (mDrawableForCursor != null) {
                 final float horizontal = getHorizontal(layout, offset);
-                return clampHorizontalPosition(mCursorDrawable, horizontal) + mTempRect.left;
+                return clampHorizontalPosition(mDrawableForCursor, horizontal) + mTempRect.left;
             }
             return super.getCursorHorizontalPosition(layout, offset);
         }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 791a8fa..24ae03c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6281,7 +6281,7 @@
             final int horizontalPadding = getCompoundPaddingLeft();
             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
 
-            if (mEditor.mCursorDrawable == null) {
+            if (mEditor.mDrawableForCursor == null) {
                 synchronized (TEMP_RECTF) {
                     /*
                      * The reason for this concern about the thickness of the
@@ -6308,7 +6308,7 @@
                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
                 }
             } else {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
             }
@@ -6360,8 +6360,8 @@
             int bottom = mLayout.getLineBottom(lineEnd);
 
             // mEditor can be null in case selection is set programmatically.
-            if (invalidateCursor && mEditor != null && mEditor.mCursorDrawable != null) {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+            if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 top = Math.min(top, bounds.top);
                 bottom = Math.max(bottom, bounds.bottom);
             }
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index ebedc89..0bc8c483 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -29,6 +29,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.internal.app.procstats.ProcessStats.*;
 
@@ -66,6 +67,8 @@
             "cch-activity", "cch-aclient", "cch-empty"
     };
 
+    // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
+    // Update states must sync enum definition as well, the ordering must not be changed.
     static final String[] ADJ_SCREEN_TAGS = new String[] {
             "0", "1"
     };
@@ -177,6 +180,13 @@
         printArrayEntry(pw, STATE_TAGS,  state, 1);
     }
 
+    public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
+            long stateId, int state) {
+        state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT);
+        state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT);
+        printProto(proto, stateId, STATE_TAGS, state, 1);
+    }
+
     public static void printAdjTag(PrintWriter pw, int state) {
         state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD);
         printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
@@ -352,6 +362,15 @@
         return value - index*mod;
     }
 
+    public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) {
+        int index = value/mod;
+        if (index >= 0 && index < array.length) {
+            // Valid state enum number starts at 1, 0 stands for unknown.
+            proto.write(fieldId, index + 1);
+        } // else enum default is always zero in proto3
+        return value - index*mod;
+    }
+
     public static String collapseString(String pkgName, String itemName) {
         if (itemName.startsWith(pkgName)) {
             final int ITEMLEN = itemName.length();
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index e0a4053..42c051d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.pm.PackageProto;
+import android.service.procstats.ProcessStatsProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -29,6 +31,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessStats.PackageState;
@@ -69,6 +73,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 public final class ProcessState {
@@ -1157,6 +1164,7 @@
         }
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
@@ -1167,4 +1175,77 @@
         sb.append("}");
         return sb.toString();
     }
+
+    public void toProto(ProtoOutputStream proto, String procName, int uid, long now) {
+        proto.write(ProcessStatsProto.PROCESS, procName);
+        proto.write(ProcessStatsProto.UID, uid);
+        if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) {
+            final long killToken = proto.start(ProcessStatsProto.KILL);
+            proto.write(ProcessStatsProto.Kill.WAKES, mNumExcessiveWake);
+            proto.write(ProcessStatsProto.Kill.CPU, mNumExcessiveCpu);
+            proto.write(ProcessStatsProto.Kill.CACHED, mNumCachedKill);
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.Kill.CACHED_PSS,
+                    mMinCachedKillPss, mAvgCachedKillPss, mMaxCachedKillPss);
+            proto.end(killToken);
+        }
+
+        // Group proc stats by type (screen state + mem state + process state)
+        Map<Integer, Long> durationByState = new HashMap<>();
+        boolean didCurState = false;
+        for (int i=0; i<mDurations.getKeyCount(); i++) {
+            final int key = mDurations.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            long time = mDurations.getValue(key);
+            if (mCurState == type) {
+                didCurState = true;
+                time += now - mStartTime;
+            }
+            durationByState.put(type, time);
+        }
+        if (!didCurState && mCurState != STATE_NOTHING) {
+            durationByState.put(mCurState, now - mStartTime);
+        }
+
+        for (int i=0; i<mPssTable.getKeyCount(); i++) {
+            final int key = mPssTable.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            if (!durationByState.containsKey(type)) {
+                // state without duration should not have stats!
+                continue;
+            }
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    type);
+
+            long duration = durationByState.get(type);
+            durationByState.remove(type); // remove the key since it is already being dumped.
+            proto.write(ProcessStatsProto.State.DURATION_MS, duration);
+
+            proto.write(ProcessStatsProto.State.SAMPLE_SIZE, mPssTable.getValue(key, PSS_SAMPLE_COUNT));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.PSS,
+                    mPssTable.getValue(key, PSS_MINIMUM),
+                    mPssTable.getValue(key, PSS_AVERAGE),
+                    mPssTable.getValue(key, PSS_MAXIMUM));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.USS,
+                    mPssTable.getValue(key, PSS_USS_MINIMUM),
+                    mPssTable.getValue(key, PSS_USS_AVERAGE),
+                    mPssTable.getValue(key, PSS_USS_MAXIMUM));
+
+            proto.end(stateToken);
+        }
+
+        for (Map.Entry<Integer, Long> entry : durationByState.entrySet()) {
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    entry.getKey());
+            proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue());
+            proto.end(stateToken);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 35b53c2..14f5e5b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -22,6 +22,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.procstats.ProcessStatsSectionProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -30,6 +31,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.procstats.DurationsTable;
@@ -1706,6 +1708,46 @@
         }
     }
 
+    public void toProto(ProtoOutputStream proto, long now) {
+        final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+
+        proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
+        proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
+                mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+        proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime);
+        proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime);
+        proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime);
+        proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss);
+        boolean partial = true;
+        if ((mFlags&FLAG_SHUTDOWN) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN);
+            partial = false;
+        }
+        if ((mFlags&FLAG_SYSPROPS) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS);
+            partial = false;
+        }
+        if ((mFlags&FLAG_COMPLETE) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE);
+            partial = false;
+        }
+        if (partial) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL);
+        }
+
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int ip=0; ip<procMap.size(); ip++) {
+            String procName = procMap.keyAt(ip);
+            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final int uid = uids.keyAt(iu);
+                final ProcessState procState = uids.valueAt(iu);
+                final long processStateToken = proto.start(ProcessStatsSectionProto.PROCESS_STATS);
+                procState.toProto(proto, procName, uid, now);
+                proto.end(processStateToken);
+            }
+        }
+    }
 
     final public static class ProcessStateHolder {
         public final int appVersion;
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index bcc7752..83ffeff 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -234,7 +234,7 @@
 }
 
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
-static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
+static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
         jint end, jboolean isRtl, jstring langTags, jlongArray hyphenators) {
     minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
@@ -245,9 +245,8 @@
             typeface);
 
     ScopedNullableUtfString langTagsString(env, langTags);
-    float result = b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start,
+    b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start,
             end, isRtl, langTagsString.get(), makeHyphenators(env, hyphenators));
-    return result;
 }
 
 static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
@@ -268,7 +267,7 @@
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
     {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
     {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph},
-    {"nAddStyleRun", "(JJIIZLjava/lang/String;[J)F", (void*) nAddStyleRun},
+    {"nAddStyleRun", "(JJIIZLjava/lang/String;[J)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JIIFLjava/lang/String;[J)V", (void*) nAddReplacementRun},
     {"nGetWidths", "(J[F)V", (void*) nGetWidths},
     {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II)I",
diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto
new file mode 100644
index 0000000..bbc1956
--- /dev/null
+++ b/core/proto/android/app/notification_channel.proto
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/media/audioattributes.proto";
+
+/**
+ * An android.app.NotificationChannel object.
+ */
+message NotificationChannelProto {
+    string id = 1;
+    string name = 2;
+    string description = 3;
+    int32 importance = 4;
+    bool can_bypass_dnd = 5;
+    // Default is VISIBILITY_NO_OVERRIDE (-1000).
+    int32 lockscreen_visibility = 6;
+    string sound = 7;
+    bool use_lights = 8;
+    // Default is 0.
+    int32 light_color = 9;
+    repeated int64 vibration = 10;
+    // Bitwise representation of fields that have been changed by the user,
+    // preventing the app from making changes to these fields.
+    int32 user_locked_fields = 11;
+    bool is_vibration_enabled = 12;
+    // Default is true.
+    bool show_badge = 13;
+    // Default is false.
+    bool is_deleted = 14;
+    string group = 15;
+    android.media.AudioAttributesProto audio_attributes = 16;
+    // If this is a blockable system notification channel.
+    bool is_blockable_system = 17;
+}
diff --git a/core/proto/android/app/notification_channel_group.proto b/core/proto/android/app/notification_channel_group.proto
new file mode 100644
index 0000000..9cb456f
--- /dev/null
+++ b/core/proto/android/app/notification_channel_group.proto
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/app/notification_channel.proto";
+
+/**
+ * An android.app.NotificationChannelGroup object.
+ */
+message NotificationChannelGroupProto {
+    string id = 1;
+    string name = 2;
+    string description = 3;
+    bool is_blocked = 4;
+    repeated android.app.NotificationChannelProto channels = 5;
+}
diff --git a/core/proto/android/media/audioattributes.proto b/core/proto/android/media/audioattributes.proto
new file mode 100644
index 0000000..3aa2792
--- /dev/null
+++ b/core/proto/android/media/audioattributes.proto
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_package = "android.media";
+option java_multiple_files = true;
+
+package android.media;
+
+/**
+ * An android.media.AudioAttributes object.
+ */
+message AudioAttributesProto {
+    Usage usage = 1;
+    ContentType content_type = 2;
+    // Bit representation of set flags.
+    int32 flags = 3;
+    repeated string tags = 4;
+}
+
+enum ContentType {
+    // Content type value to use when the content type is unknown, or other than
+    // the ones defined.
+    CONTENT_TYPE_UNKNOWN = 0;
+    // Content type value to use when the content type is speech.
+    SPEECH = 1;
+    // Content type value to use when the content type is music.
+    MUSIC = 2;
+    // Content type value to use when the content type is a soundtrack,
+    // typically accompanying a movie or TV program.
+    MOVIE = 3;
+    // Content type value to use when the content type is a sound used to
+    // accompany a user action, such as a beep or sound effect expressing a key
+    // click, or event, such as the type of a sound for a bonus being received
+    // in a game. These sounds are mostly synthesized or short Foley sounds.
+    SONIFICATION = 4;
+}
+
+enum Usage {
+    // Usage value to use when the usage is unknown.
+    USAGE_UNKNOWN = 0;
+    // Usage value to use when the usage is media, such as music, or movie
+    // soundtracks.
+    MEDIA = 1;
+    // Usage value to use when the usage is voice communications, such as
+    // telephony or VoIP.
+    VOICE_COMMUNICATION = 2;
+    // Usage value to use when the usage is in-call signalling, such as with a
+    // "busy" beep, or DTMF tones.
+    VOICE_COMMUNICATION_SIGNALLING = 3;
+    // Usage value to use when the usage is an alarm (e.g. wake-up alarm).
+    ALARM = 4;
+    // Usage value to use when the usage is notification. Other notification
+    // usages are for more specialized uses.
+    NOTIFICATION = 5;
+    // Usage value to use when the usage is telephony ringtone.
+    NOTIFICATION_RINGTONE = 6;
+    // Usage value to use when the usage is a request to enter/end a
+    // communication, such as a VoIP communication or video-conference.
+    NOTIFICATION_COMMUNICATION_REQUEST = 7;
+    // Usage value to use when the usage is notification for an "instant"
+    // communication such as a chat, or SMS.
+    NOTIFICATION_COMMUNICATION_INSTANT = 8;
+    // Usage value to use when the usage is notification for a non-immediate
+    // type of communication such as e-mail.
+    NOTIFICATION_COMMUNICATION_DELAYED = 9;
+    // Usage value to use when the usage is to attract the user's attention,
+    // such as a reminder or low battery warning.
+    NOTIFICATION_EVENT = 10;
+    // Usage value to use when the usage is for accessibility, such as with a
+    // screen reader.
+    ASSISTANCE_ACCESSIBILITY = 11;
+    // Usage value to use when the usage is driving or navigation directions.
+    ASSISTANCE_NAVIGATION_GUIDANCE = 12;
+    // Usage value to use when the usage is sonification, such as  with user
+    // interface sounds.
+    ASSISTANCE_SONIFICATION = 13;
+    // Usage value to use when the usage is for game audio.
+    GAME = 14;
+    // Usage value to use when feeding audio to the platform and replacing
+    // "traditional" audio source, such as audio capture devices.
+    VIRTUAL_SOURCE = 15;
+    // Usage value to use for audio responses to user queries, audio
+    // instructions or help utterances.
+    ASSISTANT = 16;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index e823561..c3ceb1c 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -30,6 +30,7 @@
 import "frameworks/base/core/proto/android/service/package.proto";
 import "frameworks/base/core/proto/android/service/power.proto";
 import "frameworks/base/core/proto/android/service/print.proto";
+import "frameworks/base/core/proto/android/service/procstats.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
@@ -96,4 +97,9 @@
     android.service.pm.PackageServiceDumpProto package = 3008;
     android.service.power.PowerServiceDumpProto power = 3009;
     android.service.print.PrintServiceDumpProto print = 3010;
+
+    android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "procstats --proto"
+    ];
 }
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 05afe52..d8cb1a7 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -21,6 +21,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "NotificationServiceProto";
 
+import "frameworks/base/core/proto/android/app/notification_channel.proto";
+import "frameworks/base/core/proto/android/app/notification_channel_group.proto";
 import "frameworks/base/core/proto/android/app/notificationmanager.proto";
 import "frameworks/base/core/proto/android/content/component_name.proto";
 
@@ -38,6 +40,8 @@
     ManagedServicesProto notification_assistants = 6;
 
     ManagedServicesProto condition_providers = 7;
+
+    RankingHelperProto ranking_config = 8;
 }
 
 message NotificationRecordProto {
@@ -86,6 +90,28 @@
     repeated android.content.ComponentNameProto snoozed = 5;
 }
 
+message RankingHelperProto {
+    repeated string notification_signal_extractors = 1;
+
+    message RecordProto {
+        string package = 1;
+        // Default value is UNKNOWN_UID = USER_NULL = -10000.
+        int32 uid = 2;
+        // Default is IMPORTANCE_UNSPECIFIED (-1000).
+        int32 importance = 3;
+        // Default is PRIORITY_DEFAULT (0).
+        int32 priority = 4;
+        // Default is VISIBILITY_NO_OVERRIDE (-1000).
+        int32 visibility = 5;
+        // Default is true.
+        bool show_badge = 6;
+        repeated android.app.NotificationChannelProto channels = 7;
+        repeated android.app.NotificationChannelGroupProto channel_groups = 8;
+    }
+    repeated RecordProto records = 2;
+    repeated RecordProto records_restored_without_uid = 3;
+}
+
 enum State {
     ENQUEUED = 0;
 
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
new file mode 100644
index 0000000..885150e
--- /dev/null
+++ b/core/proto/android/service/procstats.proto
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_outer_classname = "ProcessStatsServiceProto";
+
+import "frameworks/base/core/proto/android/util/common.proto";
+
+package android.service.procstats;
+
+/**
+ * Data from ProcStatsService Dumpsys
+ *
+ * Next Tag: 4
+ */
+message ProcessStatsServiceDumpProto {
+
+    ProcessStatsSectionProto procstats_now = 1;
+
+    ProcessStatsSectionProto procstats_over_3hrs = 2;
+
+    ProcessStatsSectionProto procstats_over_24hrs = 3;
+}
+
+/**
+ * Data model from /frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
+ * This proto is defined based on the writeToParcel method.
+ *
+ * Next Tag: 9
+ */
+message ProcessStatsSectionProto {
+
+    // Elapsed realtime at start of report.
+    int64 start_realtime_ms = 1;
+
+    // Elapsed realtime at end of report.
+    int64 end_realtime_ms = 2;
+
+    // CPU uptime at start of report.
+    int64 start_uptime_ms = 3;
+
+    // CPU uptime at end of report.
+    int64 end_uptime_ms = 4;
+
+    // System runtime library. e.g. "libdvm.so", "libart.so".
+    string runtime = 5;
+
+    // whether kernel reports swapped pss.
+    bool has_swapped_pss = 6;
+
+    // Data completeness. e.g. "complete", "partial", shutdown", or "sysprops".
+    enum Status {
+        STATUS_UNKNOWN = 0;
+        STATUS_COMPLETE = 1;
+        STATUS_PARTIAL = 2;
+        STATUS_SHUTDOWN = 3;
+        STATUS_SYSPROPS = 4;
+    }
+    repeated Status status = 7;
+
+    // Stats for each process.
+    repeated ProcessStatsProto process_stats = 8;
+}
+
+// Next Tag: 6
+message ProcessStatsProto {
+
+    // Name of process.
+    string process = 1;
+
+    // Uid of the process.
+    int32 uid = 2;
+
+    // Information about how often kills occurred
+    message Kill {
+      // Count of excessive wakes kills
+      int32 wakes = 1;
+
+      // Count of excessive CPU kills
+      int32 cpu = 2;
+
+      // Count of kills when cached
+      int32 cached = 3;
+
+      // PSS stats during cached kill
+      android.util.AggStats cached_pss = 4;
+    }
+    Kill kill = 3;
+
+    message State {
+        enum ScreenState {
+            SCREEN_UNKNOWN = 0;
+            OFF = 1;
+            ON = 2;
+        }
+        ScreenState screen_state = 1;
+
+        enum MemoryState {
+            MEMORY_UNKNOWN = 0;
+            NORMAL = 1;     // normal.
+            MODERATE = 2;   // moderate memory pressure.
+            LOW = 3;        // low memory.
+            CRITICAL = 4;   // critical memory.
+        }
+        MemoryState memory_state = 2;
+
+        enum ProcessState {
+            PROCESS_UNKNOWN = 0;
+            // Persistent system process.
+            PERSISTENT = 1;
+            // Top activity; actually any visible activity.
+            TOP = 2;
+            // Important foreground process (ime, wallpaper, etc).
+            IMPORTANT_FOREGROUND = 3;
+            // Important background process.
+            IMPORTANT_BACKGROUND = 4;
+            // Performing backup operation.
+            BACKUP = 5;
+            // Heavy-weight process (currently not used).
+            HEAVY_WEIGHT = 6;
+            // Background process running a service.
+            SERVICE = 7;
+            // Process not running, but would be if there was enough RAM.
+            SERVICE_RESTARTING = 8;
+            // Process running a receiver.
+            RECEIVER = 9;
+            // Process hosting home/launcher app when not on top.
+            HOME = 10;
+            // Process hosting the last app the user was in.
+            LAST_ACTIVITY = 11;
+            // Cached process hosting a previous activity.
+            CACHED_ACTIVITY = 12;
+            // Cached process hosting a client activity.
+            CACHED_ACTIVITY_CLIENT = 13;
+            // Cached process that is empty.
+            CACHED_EMPTY = 14;
+        }
+        ProcessState process_state = 3;
+
+        // Millisecond duration spent in this state
+        int64 duration_ms = 4;
+
+        // # of samples taken
+        int32 sample_size = 5;
+
+        // PSS is memory reserved for this process
+        android.util.AggStats pss = 6;
+
+        // USS is memory shared between processes, divided evenly for accounting
+        android.util.AggStats uss = 7;
+    }
+    repeated State states = 5;
+}
diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto
new file mode 100644
index 0000000..6dd4c02
--- /dev/null
+++ b/core/proto/android/util/common.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package android.util;
+
+option java_multiple_files = true;
+
+/**
+ * Very basic data structure used by aggregated stats.
+ */
+message AggStats {
+
+    int64 min = 1;
+
+    int64 average = 2;
+
+    int64 max = 3;
+}
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/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index 5548e48..b0ec55d 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -21,6 +21,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -36,6 +38,8 @@
     private static final String TEST_ID = "id";
     private static final String TEST_TITLE = "title";
     private static final String TEST_SUMMARY = "summary";
+
+    private Icon mIcon;
     private PendingIntent mTestIntent;
 
 
@@ -44,6 +48,7 @@
         final Context context = InstrumentationRegistry.getContext();
         mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
                 new Intent(), 0 /* flags */);
+        mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
     }
 
     @Test
@@ -51,12 +56,15 @@
         final Suggestion suggestion = new Suggestion.Builder(TEST_ID)
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
                 .setPendingIntent(mTestIntent)
                 .build();
 
         assertThat(suggestion.getId()).isEqualTo(TEST_ID);
         assertThat(suggestion.getTitle()).isEqualTo(TEST_TITLE);
         assertThat(suggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(suggestion.getIcon()).isEqualTo(mIcon);
+        assertThat(suggestion.getFlags()).isEqualTo(0);
         assertThat(suggestion.getPendingIntent()).isEqualTo(mTestIntent);
     }
 
@@ -66,6 +74,7 @@
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
                 .setPendingIntent(mTestIntent)
+                .setIcon(mIcon)
                 .build();
     }
 
@@ -75,6 +84,8 @@
         final Suggestion oldSuggestion = new Suggestion.Builder(TEST_ID)
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
+                .setFlags(Suggestion.FLAG_HAS_BUTTON)
                 .setPendingIntent(mTestIntent)
                 .build();
 
@@ -85,6 +96,9 @@
         assertThat(newSuggestion.getId()).isEqualTo(TEST_ID);
         assertThat(newSuggestion.getTitle()).isEqualTo(TEST_TITLE);
         assertThat(newSuggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(newSuggestion.getIcon().toString()).isEqualTo(mIcon.toString());
+        assertThat(newSuggestion.getFlags())
+                .isEqualTo(Suggestion.FLAG_HAS_BUTTON);
         assertThat(newSuggestion.getPendingIntent()).isEqualTo(mTestIntent);
     }
 }
diff --git a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
index 5f2b9e5..f7dbafa 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
@@ -53,7 +53,7 @@
     static {
         // The test font has following coverage and width.
         // U+0020: 10em
-        // U+002E (,): 10em
+        // U+002E (.): 10em
         // U+0043 (C): 100em
         // U+0049 (I): 1em
         // U+004C (L): 50em
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
index 13c3ac4..efaf224 100644
--- a/media/java/android/media/AmrInputStream.java
+++ b/media/java/android/media/AmrInputStream.java
@@ -30,7 +30,7 @@
  */
 public final class AmrInputStream extends InputStream {
     private final static String TAG = "AmrInputStream";
-    
+
     // frame is 20 msec at 8.000 khz
     private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
 
@@ -140,19 +140,15 @@
                 }
             }
 
-            // now read encoded data from the encoder (blocking, since we just filled up the
-            // encoder's input with data it should be able to output at least one buffer)
-            while (true) {
-                int index = mCodec.dequeueOutputBuffer(mInfo, -1);
-                if (index >= 0) {
-                    mBufIn = mInfo.size;
-                    ByteBuffer out = mCodec.getOutputBuffer(index);
-                    out.get(mBuf, 0 /* offset */, mBufIn /* length */);
-                    mCodec.releaseOutputBuffer(index,  false /* render */);
-                    if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        mSawOutputEOS = true;
-                    }
-                    break;
+            // now read encoded data from the encoder
+            int index = mCodec.dequeueOutputBuffer(mInfo, 0);
+            if (index >= 0) {
+                mBufIn = mInfo.size;
+                ByteBuffer out = mCodec.getOutputBuffer(index);
+                out.get(mBuf, 0 /* offset */, mBufIn /* length */);
+                mCodec.releaseOutputBuffer(index,  false /* render */);
+                if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    mSawOutputEOS = true;
                 }
             }
         }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 3b9a5de..26ead3d 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -19,12 +19,14 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.media.AudioAttributesProto;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -177,7 +179,7 @@
 
     /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
-     *            if applicable.
+     *            if applicable, as well as audioattributes.proto.
      */
 
     /**
@@ -850,6 +852,21 @@
     }
 
     /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(AudioAttributesProto.USAGE, mUsage);
+        proto.write(AudioAttributesProto.CONTENT_TYPE, mContentType);
+        proto.write(AudioAttributesProto.FLAGS, mFlags);
+        // mFormattedTags is never null due to assignment in Builder or unmarshalling.
+        for (String t : mFormattedTags.split(";")) {
+            t = t.trim();
+            if (t != "") {
+                proto.write(AudioAttributesProto.TAGS, t);
+            }
+        }
+        // TODO: is the data in mBundle useful for debugging?
+    }
+
+    /** @hide */
     public String usageToString() {
         return usageToString(mUsage);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6..9fbadee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@
         }
 
         public long getTotalBytes() {
-            return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+            return mStats.getAppBytes() + mStats.getDataBytes();
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 664dcfc..12455d8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -24,7 +24,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkKey;
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
@@ -36,10 +35,14 @@
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.provider.Settings;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -47,8 +50,12 @@
 import android.util.SparseIntArray;
 import android.widget.Toast;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,7 +71,7 @@
 /**
  * Tracks saved or available wifi networks and their state.
  */
-public class WifiTracker {
+public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     /**
      * Default maximum age in millis of cached scored networks in
      * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
@@ -80,7 +87,7 @@
      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     public static boolean sVerboseLogging;
 
-    // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
+    // TODO(b/36733768): Remove flag includeSaved
 
     // TODO: Allow control of this?
     // Combo scans can take 5-6s to complete - set to 10s.
@@ -96,9 +103,9 @@
     private final WifiListener mListener;
     private final boolean mIncludeSaved;
     private final boolean mIncludeScans;
-    private final boolean mIncludePasspoints;
-    @VisibleForTesting final MainHandler mMainHandler;
-    @VisibleForTesting final WorkHandler mWorkHandler;
+    @VisibleForTesting MainHandler mMainHandler;
+    @VisibleForTesting WorkHandler mWorkHandler;
+    private HandlerThread mWorkThread;
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
@@ -142,7 +149,7 @@
     private WifiInfo mLastInfo;
 
     private final NetworkScoreManager mNetworkScoreManager;
-    private final WifiNetworkScoreCache mScoreCache;
+    private WifiNetworkScoreCache mScoreCache;
     private boolean mNetworkScoringUiEnabled;
     private long mMaxSpeedLabelScoreCacheAge;
 
@@ -169,51 +176,43 @@
         return filter;
     }
 
+    /**
+     * Use the lifecycle constructor below whenever possible
+     */
+    @Deprecated
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, null, includeSaved, includeScans);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                       boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
+        this(context, wifiListener, includeSaved, includeScans,
                 context.getSystemService(WifiManager.class),
                 context.getSystemService(ConnectivityManager.class),
                 context.getSystemService(NetworkScoreManager.class),
-                Looper.myLooper(), newIntentFilter());
+                newIntentFilter());
+    }
+
+    public WifiTracker(Context context, WifiListener wifiListener,
+            @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
+        this(context, wifiListener, includeSaved, includeScans,
+                context.getSystemService(WifiManager.class),
+                context.getSystemService(ConnectivityManager.class),
+                context.getSystemService(NetworkScoreManager.class),
+                newIntentFilter());
+        lifecycle.addObserver(this);
     }
 
     @VisibleForTesting
-    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                boolean includeSaved, boolean includeScans, boolean includePasspoints,
-                WifiManager wifiManager, ConnectivityManager connectivityManager,
-                NetworkScoreManager networkScoreManager, Looper currentLooper,
-                IntentFilter filter) {
+    WifiTracker(Context context, WifiListener wifiListener,
+            boolean includeSaved, boolean includeScans,
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            NetworkScoreManager networkScoreManager,
+            IntentFilter filter) {
         if (!includeSaved && !includeScans) {
             throw new IllegalArgumentException("Must include either saved or scans");
         }
         mContext = context;
-        if (currentLooper == null) {
-            // When we aren't on a looper thread, default to the main.
-            currentLooper = Looper.getMainLooper();
-        }
-        mMainHandler = new MainHandler(currentLooper);
-        mWorkHandler = new WorkHandler(
-                workerLooper != null ? workerLooper : currentLooper);
+        mMainHandler = new MainHandler(Looper.getMainLooper());
         mWifiManager = wifiManager;
         mIncludeSaved = includeSaved;
         mIncludeScans = includeScans;
-        mIncludePasspoints = includePasspoints;
         mListener = wifiListener;
         mConnectivityManager = connectivityManager;
 
@@ -229,7 +228,22 @@
 
         mNetworkScoreManager = networkScoreManager;
 
-        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+        final HandlerThread workThread = new HandlerThread(TAG
+                + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        workThread.start();
+        setWorkThread(workThread);
+    }
+
+    /**
+     * Sanity warning: this wipes out mScoreCache, so use with extreme caution
+     * @param workThread substitute Handler thread, for testing purposes only
+     */
+    @VisibleForTesting
+    void setWorkThread(HandlerThread workThread) {
+        mWorkThread = workThread;
+        mWorkHandler = new WorkHandler(workThread.getLooper());
+        mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
             @Override
             public void networkCacheUpdated(List<ScoredNetwork> networks) {
                 synchronized (mLock) {
@@ -244,6 +258,11 @@
         });
     }
 
+    @Override
+    public void onDestroy() {
+        mWorkThread.quit();
+    }
+
     /** Synchronously update the list of access points with the latest information. */
     @MainThread
     public void forceUpdate() {
@@ -312,8 +331,9 @@
      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
      * then forceUpdate() must be called to populate getAccessPoints().
      */
+    @Override
     @MainThread
-    public void startTracking() {
+    public void onStart() {
         synchronized (mLock) {
             registerScoreCache();
 
@@ -361,15 +381,16 @@
     /**
      * Stop tracking wifi networks and scores.
      *
-     * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
+     * <p>This should always be called when done with a WifiTracker (if onStart was called) to
      * ensure proper cleanup and prevent any further callbacks from occurring.
      *
      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
      */
+    @Override
     @MainThread
-    public void stopTracking() {
+    public void onStop() {
         synchronized (mLock) {
             if (mRegistered) {
                 mContext.unregisterReceiver(mReceiver);
@@ -769,9 +790,8 @@
     }
 
     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
-            boolean includeScans, boolean includePasspoints) {
-        WifiTracker tracker = new WifiTracker(context,
-                null, null, includeSaved, includeScans, includePasspoints);
+            boolean includeScans) {
+        WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
         tracker.forceUpdate();
         tracker.copyAndNotifyListeners(false /*notifyListeners*/);
         return tracker.getAccessPoints();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index 79cee04..8b5863a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,8 +16,10 @@
 package com.android.settingslib.wifi;
 
 import android.content.Context;
-import android.os.Looper;
 import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 /**
  * Factory method used to inject WifiTracker instances.
@@ -31,12 +33,11 @@
     }
 
     public static WifiTracker create(
-            Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+            Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle,
+            boolean includeSaved, boolean includeScans) {
         if(sTestingWifiTracker != null) {
             return sTestingWifiTracker;
         }
-        return new WifiTracker(
-                context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+        return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans);
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
new file mode 100644
index 0000000..3dabe99
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.StorageStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StorageStatsSourceTest {
+    @Test
+    public void AppStorageStatsImpl_totalCorrectly() {
+        StorageStats storageStats = new StorageStats();
+        storageStats.cacheBytes = 1;
+        storageStats.codeBytes = 10;
+        storageStats.dataBytes = 100;
+        StorageStatsSource.AppStorageStatsImpl stats = new StorageStatsSource.AppStorageStatsImpl(
+                storageStats);
+
+        // Note that this does not double add the cache (111).
+        assertThat(stats.getTotalBytes()).isEqualTo(110);
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index f25bb28..4a1d392 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -54,7 +54,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
@@ -147,10 +146,7 @@
     private CountDownLatch mAccessPointsChangedLatch;
     private CountDownLatch mRequestScoresLatch;
     private Handler mScannerHandler;
-    private HandlerThread mMainThread;
     private HandlerThread mWorkerThread;
-    private Looper mWorkerLooper;
-    private Looper mMainLooper;
 
     private int mOriginalScoringUiSettingValue;
 
@@ -162,10 +158,6 @@
 
         mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
         mWorkerThread.start();
-        mWorkerLooper = mWorkerThread.getLooper();
-        mMainThread = new HandlerThread("TestHandlerThread");
-        mMainThread.start();
-        mMainLooper = mMainThread.getLooper();
 
         // Make sure the scanner doesn't try to run on the testing thread.
         HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread");
@@ -283,18 +275,17 @@
     }
 
     private WifiTracker createMockedWifiTracker() {
-        return new WifiTracker(
+        final WifiTracker wifiTracker = new WifiTracker(
                 mContext,
                 mockWifiListener,
-                mWorkerLooper,
-                true,
                 true,
                 true,
                 mockWifiManager,
                 mockConnectivityManager,
                 mockNetworkScoreManager,
-                mMainLooper,
                 new IntentFilter()); // empty filter to ignore system broadcasts
+        wifiTracker.setWorkThread(mWorkerThread);
+        return wifiTracker;
     }
 
     private void startTracking(WifiTracker tracker)  throws InterruptedException {
@@ -302,7 +293,7 @@
         mScannerHandler.post(new Runnable() {
             @Override
             public void run() {
-                tracker.startTracking();
+                tracker.onStart();
                 latch.countDown();
             }
         });
@@ -406,7 +397,7 @@
         scanResult.capabilities = "";
 
         WifiTracker tracker = new WifiTracker(
-                InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
+                InstrumentationRegistry.getTargetContext(), null, true, true);
 
         AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
         assertTrue(result.mAccessPointListener != null);
@@ -422,7 +413,7 @@
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
 
         WifiTracker tracker = new WifiTracker(
-                InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
+                InstrumentationRegistry.getTargetContext(), null, true, true);
 
         AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
         assertTrue(result.mAccessPointListener != null);
@@ -452,7 +443,7 @@
                         .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
 
         // Test unregister
-        tracker.stopTracking();
+        tracker.onStop();
 
         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
         verify(mockNetworkScoreManager)
@@ -496,7 +487,7 @@
         // Start the tracker and inject the initial scan results and then stop tracking
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
 
-        tracker.stopTracking();
+        tracker.onStop();
         mRequestedKeys.clear();
 
         mRequestScoresLatch = new CountDownLatch(1);
@@ -515,7 +506,7 @@
         // Start the tracker and inject the initial scan results and then stop tracking
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
         updateScoresAndWaitForAccessPointsChangedCallback(tracker);
-        tracker.stopTracking();
+        tracker.onStop();
 
         assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
     }
@@ -675,7 +666,7 @@
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
         WifiNetworkScoreCache cache = mScoreCacheCaptor.getValue();
 
-        tracker.stopTracking();
+        tracker.onStop();
         verify(mockNetworkScoreManager).unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, cache);
 
         // Verify listener is unregistered so updating a score does not throw an error by posting
@@ -795,7 +786,7 @@
         tracker.mMainHandler.sendEmptyMessage(
                 WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED);
 
-        tracker.stopTracking();
+        tracker.onStop();
 
         verify(mockWifiListener, atMost(1)).onAccessPointsChanged();
         verify(mockWifiListener, atMost(1)).onConnectedChanged();
@@ -821,7 +812,7 @@
         startTracking(tracker);
         waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
-        tracker.stopTracking();
+        tracker.onStop();
         waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
         startTracking(tracker);
diff --git a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
index db2eb3a..bff97f6 100644
--- a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
+++ b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
@@ -17,6 +17,6 @@
 
       <corners android:radius="@dimen/borderless_button_radius" />
 
-      <solid android:color="#CC000000" />
+      <solid android:color="?attr/clearAllBackgroundColor" />
 
 </shape>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 16c74e3..c7edb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -203,10 +203,6 @@
                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
                         mStackButtonShadowColor);
             }
-            if (Recents.getConfiguration().isLowRamDevice) {
-                int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
-                mStackActionButton.setBackgroundColor(bgColor);
-            }
         }
 
         // Let's also require dark status and nav bars if the text is dark
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 54e9ed9..492ab44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -18,8 +18,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
@@ -30,6 +33,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.service.notification.SnoozeCriterion;
@@ -63,6 +67,15 @@
     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 static final LogMaker OPTIONS_OPEN_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_OPEN);
+    private static final LogMaker OPTIONS_CLOSE_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_CLOSE);
+    private static final LogMaker UNDO_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+                    .setType(MetricsEvent.TYPE_ACTION);
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -88,6 +101,8 @@
             R.id.action_snooze_longer,
     };
 
+    private MetricsLogger mMetricsLogger = new MetricsLogger();
+
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
         mParser = new KeyValueListParser(',');
@@ -123,7 +138,13 @@
         mSnoozeOptions = getDefaultSnoozeOptions();
         createOptionViews();
 
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
     }
 
     @Override
@@ -163,7 +184,7 @@
             SnoozeOption so = mSnoozeOptions.get(i);
             if (so.getAccessibilityAction() != null
                     && so.getAccessibilityAction().getId() == action) {
-                setSelected(so);
+                setSelected(so, true);
                 return true;
             }
         }
@@ -327,12 +348,24 @@
         mExpandAnimation.start();
     }
 
-    private void setSelected(SnoozeOption option) {
+    private void setSelected(SnoozeOption option, boolean userAction) {
         mSelectedOption = option;
         mSelectedOptionText.setText(option.getConfirmation());
         showSnoozeOptions(false);
         hideSelectedOption();
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        if (userAction) {
+            logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+        }
+    }
+
+    private void logOptionSelection(int category, SnoozeOption option) {
+        int index = mSnoozeOptions.indexOf(option);
+        long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+        mMetricsLogger.write(new LogMaker(category)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
     }
 
     @Override
@@ -343,13 +376,15 @@
         final int id = v.getId();
         final SnoozeOption tag = (SnoozeOption) v.getTag();
         if (tag != null) {
-            setSelected(tag);
+            setSelected(tag, true);
         } else if (id == R.id.notification_snooze) {
             // Toggle snooze options
             showSnoozeOptions(!mExpanded);
+            mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
         } else {
             // Undo snooze was selected
             undoSnooze(v);
+            mMetricsLogger.write(UNDO_LOG);
         }
     }
 
@@ -380,7 +415,7 @@
     @Override
     public View getContentView() {
         // Reset the view before use
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
         return this;
     }
 
@@ -402,7 +437,7 @@
             return true;
         } else {
             // The view should actually be closed
-            setSelected(mSnoozeOptions.get(0));
+            setSelected(mSnoozeOptions.get(0), false);
             return false; // Return false here so that guts handles closing the view
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a2e5a3b..54b4e35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1437,6 +1437,11 @@
             animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
         };
 
+        if (hideAnimatedList.isEmpty()) {
+            animationFinishAction.run();
+            return;
+        }
+
         // let's disable our normal animations
         mStackScroller.setDismissAllInProgress(true);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a6837..0d21c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,13 +58,19 @@
 
     private int mCurrentUser;
 
-    public AccessPointControllerImpl(Context context, Looper bgLooper) {
+    public AccessPointControllerImpl(Context context) {
         mContext = context;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+        mWifiTracker = new WifiTracker(context, this, false, true);
         mCurrentUser = ActivityManager.getCurrentUser();
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        mWifiTracker.onDestroy();
+    }
+
     public boolean canConfigWifi() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
-            mWifiTracker.startTracking();
+            mWifiTracker.onStart();
         }
     }
 
@@ -91,7 +96,7 @@
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         mCallbacks.remove(callback);
         if (mCallbacks.isEmpty()) {
-            mWifiTracker.stopTracking();
+            mWifiTracker.onStop();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 3e9d000..d24e51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -150,7 +150,7 @@
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
-                new AccessPointControllerImpl(context, bgLooper),
+                new AccessPointControllerImpl(context),
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 401705d..e17a6a6 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4538,103 +4538,132 @@
     // OS: O MR
     AUTOFILL_UI_LATENCY = 1136;
 
+    // Action: the snooze leave-behind was shown after the user clicked the snooze icon
+    // OS: O MR
+    NOTIFICATION_SNOOZE_CLICKED = 1137;
+
+    // Action: user selected a notification snooze duration from the drop down
+    // OS: O MR
+    NOTIFICATION_SELECT_SNOOZE = 1138;
+
+    // attached to NOTIFICATION_SNOOZED and NOTIFICATION_SELECT_SNOOZE events
+    // OS: O MR
+    FIELD_NOTIFICATION_SNOOZE_DURATION_MS = 1139;
+
+    // attached to NOTIFICATION_SELECT_SNOOZE events to indicate the option selected
+    // OS: O MR
+    FIELD_NOTIFICATION_SNOOZE_INDEX = 1140;
+
+    // Action: user tapped undo on the notification snooze leave-behind
+    // OS: O MR
+    NOTIFICATION_UNDO_SNOOZE = 1141;
+
+    // Action: user togged the visibility of the notification snooze options drop down
+    // OS: O MR
+    NOTIFICATION_SNOOZE_OPTIONS = 1142;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // OPEN: Settings > Network & Internet > Mobile network
     // CATEGORY: SETTINGS
-    SETTINGS_MOBILE_NETWORK_CATEGORY = 1139;
+    SETTINGS_MOBILE_NETWORK_CATEGORY = 1200;
 
     // ACTION: Settings > Network & Internet > Mobile network > Roaming
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_DATA_ROAMING_TOGGLE = 1140;
+    ACTION_MOBILE_NETWORK_DATA_ROAMING_TOGGLE = 1201;
 
     // ACTION: Settings > Network & Internet > Mobile network > Advanced
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_EXPAND_ADVANCED_FIELDS = 1141;
+    ACTION_MOBILE_NETWORK_EXPAND_ADVANCED_FIELDS = 1202;
 
     // ACTION: Settings > Network & Internet > Mobile network > Enhanced 4G LTE Mode
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_ENHANCED_4G_LTE_MODE_TOGGLE = 1142;
+    ACTION_MOBILE_ENHANCED_4G_LTE_MODE_TOGGLE = 1203;
 
     // ACTION: Settings > Network & Internet > Mobile network > Preferred network type
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SELECT_PREFERRED_NETWORK = 1143;
+    ACTION_MOBILE_NETWORK_SELECT_PREFERRED_NETWORK = 1204;
 
     // ACTION: Settings > Network & Internet > Mobile network > Preferred network type (enabled networks)
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SELECT_ENABLED_NETWORK = 1144;
+    ACTION_MOBILE_NETWORK_SELECT_ENABLED_NETWORK = 1205;
 
     // OPEN: Settings > Network & Internet > Mobile network > Carrier
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_EUICC_SETTING = 1145;
+    ACTION_MOBILE_NETWORK_EUICC_SETTING = 1206;
 
     // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_WIFI_CALLING = 1146;
+    ACTION_MOBILE_NETWORK_WIFI_CALLING = 1207;
 
     // ACTION: Settings > Network & Internet > Mobile network > Carrier video calling
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_VIDEO_CALLING_TOGGLE = 1147;
+    ACTION_MOBILE_NETWORK_VIDEO_CALLING_TOGGLE = 1208;
 
     // ACTION: Settings > Network & Internet > Mobile network > Automatically select network
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_AUTO_SELECT_NETWORK_TOGGLE = 1148;
+    ACTION_MOBILE_NETWORK_AUTO_SELECT_NETWORK_TOGGLE = 1209;
 
     // ACTION: Settings > Network & Internet > Mobile network > Network
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK = 1149;
+    ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK = 1210;
 
     // FIELD - Manually selected mobile network
-    FIELD_MOBILE_NETWORK = 1150;
+    FIELD_MOBILE_NETWORK = 1211;
 
     // OPEN: Settings > Network & Internet > Mobile network > Access Point Names
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_APN_SETTINGS = 1151;
+    ACTION_MOBILE_NETWORK_APN_SETTINGS = 1212;
 
     // OPEN: Settings > Network & Internet > Mobile network > Carrier settings
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CARRIER_SETTINGS = 1152;
+    ACTION_MOBILE_NETWORK_CARRIER_SETTINGS = 1213;
 
     // OPEN: Settings > Network & Internet > Mobile network > System select
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CDMA_SYSTEM_SELECT = 1153;
+    ACTION_MOBILE_NETWORK_CDMA_SYSTEM_SELECT = 1214;
 
     // OPEN: Settings > Network & Internet > Mobile network > CDMA subscription
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CDMA_SUBSCRIPTION_SELECT = 1154;
+    ACTION_MOBILE_NETWORK_CDMA_SUBSCRIPTION_SELECT = 1215;
 
     // ACTION: Settings > Network & Internet > Mobile network > Set up data service
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SET_UP_DATA_SERVICE = 1155;
+    ACTION_MOBILE_NETWORK_SET_UP_DATA_SERVICE = 1216;
 
     // OPEN: Settings > Developer Options > Experiment dashboard
     // CATEGORY: SETTINGS
-    SETTINGS_FEATURE_FLAGS_DASHBOARD = 1156;
+    SETTINGS_FEATURE_FLAGS_DASHBOARD = 1217;
 
     // OPEN: Settings > Notifications > [App] > Topic Notifications
     // CATEGORY: SETTINGS
     // OS: P
-    NOTIFICATION_CHANNEL_GROUP = 1157;
+    NOTIFICATION_CHANNEL_GROUP = 1218;
 
     // OPEN: Settings > Developer options > Enable > Info dialog
     // CATEGORY: SETTINGS
     // OS: P
-    DIALOG_ENABLE_DEVELOPMENT_OPTIONS = 1158;
+    DIALOG_ENABLE_DEVELOPMENT_OPTIONS = 1219;
 
     // OPEN: Settings > Developer options > OEM unlocking > Info dialog
     // CATEGORY: SETTINGS
     // OS: P
-    DIALOG_ENABLE_OEM_UNLOCKING = 1159;
+    DIALOG_ENABLE_OEM_UNLOCKING = 1220;
 
     // OPEN: Settings > Security > Nexus Imprint > [Fingerprint]
     // CATEGORY: SETTINGS
     // OS: P
-    FINGERPRINT_AUTHENTICATE_SIDECAR = 1160;
+    FINGERPRINT_AUTHENTICATE_SIDECAR = 1221;
 
     // OPEN: Settings > Developer options > USB debugging > Info dialog
     // CATEGORY: SETTINGS
     // OS: P
-    DIALOG_ENABLE_ADB = 1161;
+    DIALOG_ENABLE_ADB = 1222;
+
+    // OPEN: Settings > Developer options > Revoke USB debugging authorizations > Info dialog
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_CLEAR_ADB_KEYS = 1223;
 
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 39aed7c..effb86c 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -23,11 +23,14 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.DumpUtils;
@@ -622,13 +625,17 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            dumpInner(fd, pw, args);
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpInner(pw, args);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void dumpInner(PrintWriter pw, String[] args) {
         final long now = SystemClock.uptimeMillis();
 
         boolean isCheckin = false;
@@ -1038,4 +1045,44 @@
             }
         }
     }
+
+    private void dumpAggregatedStats(ProtoOutputStream proto, int aggregateHours, long now) {
+        ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+                - (ProcessStats.COMMIT_PERIOD/2));
+        if (pfd == null) {
+            return;
+        }
+        ProcessStats stats = new ProcessStats(false);
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        stats.read(stream);
+        if (stats.mReadError != null) {
+            return;
+        }
+        stats.toProto(proto, now);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // dump current procstats
+        long nowToken = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
+        long now;
+        synchronized (mAm) {
+            now = SystemClock.uptimeMillis();
+            mProcessStats.toProto(proto, now);
+        }
+        proto.end(nowToken);
+
+        // aggregated over last 3 hours procstats
+        long tokenOf3Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_3HRS);
+        dumpAggregatedStats(proto, 3, now);
+        proto.end(tokenOf3Hrs);
+
+        // aggregated over last 24 hours procstats
+        long tokenOf24Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_24HRS);
+        dumpAggregatedStats(proto, 24, now);
+        proto.end(tokenOf24Hrs);
+
+        proto.flush();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 027dc08..ac85e6b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -517,11 +517,14 @@
                             } catch (PackageManager.NameNotFoundException e) {
                             }
                         }
-                        if (localForegroundNoti.getSmallIcon() == null) {
+                        if (localForegroundNoti.getSmallIcon() == null
+                                || nm.getNotificationChannel(localPackageName, appUid,
+                                localForegroundNoti.getChannelId()) == null) {
                             // Notifications whose icon is 0 are defined to not show
                             // a notification, silently ignoring it.  We don't want to
                             // just ignore it, we want to prevent the service from
                             // being foreground.
+                            // Also every notification needs a channel.
                             throw new RuntimeException("invalid service notification: "
                                     + foregroundNoti);
                         }
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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 90dab2c..b4056b3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -331,7 +331,6 @@
     private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
     private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
     private static final int MSG_POLICIES_CHANGED = 13;
-    private static final int MSG_SET_FIREWALL_RULES = 14;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3138,9 +3137,9 @@
                     uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                 }
             }
-            setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+            setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
         } else {
-            setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+            setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
         }
     }
 
@@ -3207,7 +3206,7 @@
                 }
             }
 
-            setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -3906,18 +3905,6 @@
                     removeInterfaceQuota((String) msg.obj);
                     return true;
                 }
-                case MSG_SET_FIREWALL_RULES: {
-                    final int chain = msg.arg1;
-                    final int toggle = msg.arg2;
-                    final SparseIntArray uidRules = (SparseIntArray) msg.obj;
-                    if (uidRules != null) {
-                        setUidFirewallRules(chain, uidRules);
-                    }
-                    if (toggle != CHAIN_TOGGLE_NONE) {
-                        enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
-                    }
-                    return true;
-                }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
                     resetUidFirewallRules(msg.arg1);
                     return true;
@@ -4063,15 +4050,20 @@
 
     /**
      * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
-     * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+     * {@link #enableFirewallChainUL(int, boolean)} synchronously.
      *
      * @param chain firewall chain.
      * @param uidRules new UID rules; if {@code null}, only toggles chain state.
      * @param toggle whether the chain should be enabled, disabled, or not changed.
      */
-    private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+    private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
             @ChainToggleType int toggle) {
-        mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+        if (uidRules != null) {
+            setUidFirewallRulesUL(chain, uidRules);
+        }
+        if (toggle != CHAIN_TOGGLE_NONE) {
+            enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+        }
     }
 
     /**
@@ -4079,7 +4071,7 @@
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
-    private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+    private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
         try {
             int size = uidRules.size();
             int[] uids = new int[size];
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 4923b06..f1476b3 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 
 public interface NotificationManagerInternal {
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int userId);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6e84dd2..4cb5d0f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3286,6 +3286,10 @@
             long conditionsToken = proto.start(NotificationServiceDumpProto.CONDITION_PROVIDERS);
             mConditionProviders.dump(proto, filter);
             proto.end(conditionsToken);
+
+            long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
+            mRankingHelper.dump(proto, filter);
+            proto.end(rankingToken);
         }
 
         proto.flush();
@@ -3441,6 +3445,12 @@
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
         @Override
+        public NotificationChannel getNotificationChannel(String pkg, int uid, String
+                channelId) {
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+        }
+
+        @Override
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
                 String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3807,6 +3817,8 @@
             MetricsLogger.action(r.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
                     .setType(MetricsEvent.TYPE_CLOSE)
+                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+                            mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
             boolean wasPosted = removeFromNotificationListsLocked(r);
@@ -5828,10 +5840,9 @@
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
                 final String a = args[ai];
-                if ("--proto".equals(args[0])) {
+                if ("--proto".equals(a)) {
                     filter.proto = true;
-                }
-                if ("--noredact".equals(a) || "--reveal".equals(a)) {
+                } else if ("--noredact".equals(a) || "--reveal".equals(a)) {
                     filter.redact = false;
                 } else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
                     if (ai < args.length-1) {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index fea2464..8783f47 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -36,10 +36,13 @@
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -915,8 +918,7 @@
                 pw.print("  ");
                 pw.println(mSignalExtractors[i]);
             }
-        }
-        if (filter == null) {
+
             pw.print(prefix);
             pw.println("per-package config:");
         }
@@ -928,6 +930,52 @@
         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
+    public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
+                mSignalExtractors[i].getClass().getSimpleName());
+        }
+        synchronized (mRecords) {
+            dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
+        }
+        dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+            mRestoredWithoutUids);
+    }
+
+    private static void dumpRecords(ProtoOutputStream proto, long fieldId,
+            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+        final int N = records.size();
+        long fToken;
+        for (int i = 0; i < N; i++) {
+            final Record r = records.valueAt(i);
+            if (filter == null || filter.matches(r.pkg)) {
+                fToken = proto.start(fieldId);
+
+                proto.write(RecordProto.PACKAGE, r.pkg);
+                proto.write(RecordProto.UID, r.uid);
+                proto.write(RecordProto.IMPORTANCE, r.importance);
+                proto.write(RecordProto.PRIORITY, r.priority);
+                proto.write(RecordProto.VISIBILITY, r.visibility);
+                proto.write(RecordProto.SHOW_BADGE, r.showBadge);
+
+                long token;
+                for (NotificationChannel channel : r.channels.values()) {
+                    token = proto.start(RecordProto.CHANNELS);
+                    channel.toProto(proto);
+                    proto.end(token);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    token = proto.start(RecordProto.CHANNEL_GROUPS);
+                    group.toProto(proto);
+                    proto.end(token);
+                }
+
+                proto.end(fToken);
+            }
+        }
+    }
+
     private static void dumpRecords(PrintWriter pw, String prefix,
             NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
         final int N = records.size();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d7ec7b6..92cbd3d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -668,9 +668,11 @@
         traceEnd();
 
         // Tracks whether the updatable WebView is in a ready state and watches for update installs.
-        traceBeginAndSlog("StartWebViewUpdateService");
-        mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
-        traceEnd();
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            traceBeginAndSlog("StartWebViewUpdateService");
+            mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
+            traceEnd();
+        }
     }
 
     /**
@@ -1091,6 +1093,14 @@
                     traceBeginAndSlog("StartWifiRtt");
                     mSystemServiceManager.startService("com.android.server.wifi.RttService");
                     traceEnd();
+
+                    if (context.getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_WIFI_RTT)) {
+                        traceBeginAndSlog("StartRttService");
+                        mSystemServiceManager.startService(
+                                "com.android.server.wifi.rtt.RttService");
+                        traceEnd();
+                    }
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1098,8 +1108,6 @@
                     traceBeginAndSlog("StartWifiAware");
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                     traceEnd();
-                } else {
-                    Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1687,10 +1695,10 @@
             traceEnd();
 
             // No dependency on Webview preparation in system server. But this should
-            // be completed before allowring 3rd party
+            // be completed before allowing 3rd party
             final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
             Future<?> webviewPrep = null;
-            if (!mOnlyCore) {
+            if (!mOnlyCore && mWebViewUpdateService != null) {
                 webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
                     Slog.i(TAG, WEBVIEW_PREPARATION);
                     TimingsTraceLog traceLog = new TimingsTraceLog(
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index ebac041..764b7b2 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -522,8 +522,7 @@
      * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
      */
     public void registerStateCallback(@NonNull DownloadRequest request,
-            @NonNull DownloadStateCallback callback,
-            @NonNull Handler handler) {
+            @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -533,7 +532,8 @@
                 new InternalDownloadStateCallback(callback, handler);
 
         try {
-            int result = downloadService.registerStateCallback(request, internalCallback);
+            int result = downloadService.registerStateCallback(request, internalCallback,
+                    callback.getCallbackFilterFlags());
             if (result != MbmsErrors.SUCCESS) {
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
diff --git a/telephony/java/android/telephony/mbms/DownloadStateCallback.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
index 86920bd..892fbf0 100644
--- a/telephony/java/android/telephony/mbms/DownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
@@ -16,8 +16,12 @@
 
 package android.telephony.mbms;
 
+import android.annotation.IntDef;
 import android.telephony.MbmsDownloadSession;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A optional listener class used by download clients to track progress. Apps should extend this
  * class and pass an instance into
@@ -29,6 +33,71 @@
 public class DownloadStateCallback {
 
     /**
+     * Bitmask flags used for filtering out callback methods. Used when constructing the
+     * DownloadStateCallback as an optional parameter.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+    public @interface FilterFlag {}
+
+    /**
+     * Receive all callbacks.
+     * Default value.
+     */
+    public static final int ALL_UPDATES = 0x00;
+    /**
+     * Receive callbacks for {@link #onProgressUpdated}.
+     */
+    public static final int PROGRESS_UPDATES = 0x01;
+    /**
+     * Receive callbacks for {@link #onStateUpdated}.
+     */
+    public static final int STATE_UPDATES = 0x02;
+
+    private final int mCallbackFilterFlags;
+
+    /**
+     * Creates a DownloadStateCallback that will receive all callbacks.
+     */
+    public DownloadStateCallback() {
+        mCallbackFilterFlags = ALL_UPDATES;
+    }
+
+    /**
+     * Creates a DownloadStateCallback that will only receive callbacks for the methods specified
+     * via the filterFlags parameter.
+     * @param filterFlags A bitmask of filter flags that will specify which callback this instance
+     *     is interested in.
+     */
+    public DownloadStateCallback(int filterFlags) {
+        mCallbackFilterFlags = filterFlags;
+    }
+
+    /**
+     * Return the currently set filter flags.
+     * @return An integer containing the bitmask of flags that this instance is interested in.
+     * @hide
+     */
+    public int getCallbackFilterFlags() {
+        return mCallbackFilterFlags;
+    }
+
+    /**
+     * Returns true if a filter flag is set for a particular callback method. If the flag is set,
+     * the callback will be delivered to the listening process.
+     * @param flag A filter flag specifying whether or not a callback method is registered to
+     *     receive callbacks.
+     * @return true if registered to receive callbacks in the listening process, false if not.
+     */
+    public final boolean isFilterFlagSet(@FilterFlag int flag) {
+        if (mCallbackFilterFlags == ALL_UPDATES) {
+            return true;
+        }
+        return (mCallbackFilterFlags & flag) > 0;
+    }
+
+    /**
      * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
      *
      * @param request a {@link DownloadRequest}, indicating which download is being referenced.
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/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index ed5e826..cb93542 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -36,7 +36,8 @@
 
     int download(in DownloadRequest downloadRequest);
 
-    int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
+    int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener,
+        int flags);
 
     int unregisterStateCallback(in DownloadRequest downloadRequest,
         IDownloadStateCallback listener);
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index d845a57..2f85a1d 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -46,6 +46,47 @@
     private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
 
+
+    // Filters the DownloadStateCallbacks by its configuration from the app.
+    private abstract static class FilteredDownloadStateCallback extends DownloadStateCallback {
+
+        private final IDownloadStateCallback mCallback;
+        public FilteredDownloadStateCallback(IDownloadStateCallback callback, int callbackFlags) {
+            super(callbackFlags);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+                int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
+                int fullDecodedSize) {
+            if (!isFilterFlagSet(PROGRESS_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+                        fullDownloadSize, currentDecodedSize, fullDecodedSize);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        @Override
+        public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+                @MbmsDownloadSession.DownloadStatus int state) {
+            if (!isFilterFlagSet(STATE_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onStateUpdated(request, fileInfo, state);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        protected abstract void onRemoteException(RemoteException e);
+    }
+
     /**
      * Initialize the download service for this app and subId, registering the listener.
      *
@@ -196,9 +237,8 @@
      * @hide
      */
     @Override
-    public final int registerStateCallback(
-            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
-            throws RemoteException {
+    public final int registerStateCallback(final DownloadRequest downloadRequest,
+            final IDownloadStateCallback callback, int flags) throws RemoteException {
         final int uid = Binder.getCallingUid();
         DeathRecipient deathRecipient = new DeathRecipient() {
             @Override
@@ -211,28 +251,10 @@
         mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
         callback.asBinder().linkToDeath(deathRecipient, 0);
 
-        DownloadStateCallback exposedCallback = new DownloadStateCallback() {
+        DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
             @Override
-            public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
-                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
-                    fullDecodedSize) {
-                try {
-                    callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
-                            fullDownloadSize,
-                            currentDecodedSize, fullDecodedSize);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
-            }
-
-            @Override
-            public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
-                    @MbmsDownloadSession.DownloadStatus int state) {
-                try {
-                    callback.onStateUpdated(request, fileInfo, state);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
+            protected void onRemoteException(RemoteException e) {
+                onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
             }
         };
 
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/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/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/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 10a4587..6b97ae6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -18,6 +18,7 @@
 
 #include <string>
 
+#include "flatten/XmlFlattener.h"
 #include "io/StringInputStream.h"
 #include "test/Test.h"
 
@@ -51,6 +52,36 @@
   EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android"));
 }
 
+TEST(XmlDomTest, BinaryInflate) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<XmlResource> doc = util::make_unique<XmlResource>();
+  doc->root = util::make_unique<Element>();
+  doc->root->name = "Layout";
+  doc->root->line_number = 2u;
+
+  NamespaceDecl decl;
+  decl.uri = kSchemaAndroid;
+  decl.prefix = "android";
+  decl.line_number = 2u;
+  doc->root->namespace_decls.push_back(decl);
+
+  BigBuffer buffer(4096);
+  XmlFlattener flattener(&buffer, {});
+  ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
+
+  auto block = util::Copy(buffer);
+  std::unique_ptr<XmlResource> new_doc =
+      Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+  ASSERT_THAT(new_doc, NotNull());
+
+  EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
+  EXPECT_THAT(new_doc->root->line_number, Eq(2u));
+  ASSERT_THAT(new_doc->root->namespace_decls, SizeIs(1u));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].uri, StrEq(kSchemaAndroid));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].prefix, StrEq("android"));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].line_number, Eq(2u));
+}
+
 // Escaping is handled after parsing of the values for resource-specific values.
 TEST(XmlDomTest, ForwardEscapes) {
   std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(
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/java/android/net/wifi/rtt/package.html b/wifi/java/android/net/wifi/rtt/package.html
new file mode 100644
index 0000000..221b94b
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/package.html
@@ -0,0 +1,39 @@
+<HTML>
+<BODY>
+<p>Provides classes which allow applications to use Wi-Fi RTT (IEEE 802.11mc) to measure distance
+    to supporting Access Points and peer devices.</p>
+<p>The primary entry point to Wi-Fi RTT capabilities is the
+    {@link android.net.wifi.rtt.WifiRttManager} class, which is acquired by calling
+    {@link android.content.Context#getSystemService(String)
+    Context.getSystemService(Context.WIFI_RTT_SERVICE)}</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+    <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi RTT
+    functionality.
+    If your application only works with Wi-Fi RTT (i.e. it should only be installed on devices which
+    support Wi-Fi RTT), declare so with a <a
+            href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+        {@code <uses-feature>}</a>
+    element in the manifest file:</p>
+<pre>
+&lt;manifest ...>
+    &lt;uses-feature android:name="android.hardware.wifi.rtt" />
+    ...
+&lt;/manifest>
+</pre>
+<p>Alternatively, if your application does not require Wi-Fi RTT but can take advantage of it if
+    available, you can perform
+    the check at run-time in your code using {@link
+    android.content.pm.PackageManager#hasSystemFeature(String)} with {@link
+    android.content.pm.PackageManager#FEATURE_WIFI_RTT}:</p>
+<pre>
+    getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)
+</pre>
+</BODY>
+</HTML>
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);
+    }
+}