Merge "Improve TextView.onMeasure() for multiline text."
diff --git a/Android.bp b/Android.bp
index eb35ffb..33acffa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,7 @@
             // needed by the device.
             srcs: [
                 "core/proto/android/os/kernelwake.proto",
+                "core/proto/android/os/procrank.proto",
                 "core/proto/android/service/graphicsstats.proto",
             ],
             shared: {
diff --git a/Android.mk b/Android.mk
index 0642129..452bbf2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -602,8 +602,10 @@
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp bouncycastle ext
 
-LOCAL_STATIC_JAVA_LIBRARIES :=                          \
-    framework-protos                                    \
+LOCAL_STATIC_JAVA_LIBRARIES :=                           \
+    framework-protos                                     \
+    android.hidl.base-V1.0-java                          \
+    android.hardware.cas-V1.0-java                       \
     android.hardware.health-V1.0-java-constants          \
     android.hardware.thermal-V1.0-java-constants         \
     android.hardware.tv.input-V1.0-java-constants        \
@@ -613,8 +615,6 @@
     android.hardware.vibrator-V1.1-java-constants        \
     android.hardware.wifi-V1.0-java-constants            \
 
-include hardware/interfaces/cas/1.0/CasHal.mk
-
 # Loaded with System.loadLibrary by android.view.textclassifier
 LOCAL_REQUIRED_MODULES += libtextclassifier
 
diff --git a/api/current.txt b/api/current.txt
index 586637b..80b7282 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13829,6 +13829,7 @@
   public class Typeface {
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+    method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
     method public static android.graphics.Typeface createFromFile(java.io.File);
     method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -22742,6 +22743,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -51646,7 +51648,9 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public boolean isAllCaps();
     method public boolean isCursorVisible();
+    method public boolean isElegantTextHeight();
     method public boolean isInputMethodTarget();
     method public boolean isSuggestionsEnabled();
     method public boolean isTextSelectable();
diff --git a/api/system-current.txt b/api/system-current.txt
index 2be0361..e393ed2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14627,6 +14627,7 @@
   public class Typeface {
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+    method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
     method public static android.graphics.Typeface createFromFile(java.io.File);
     method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -24688,6 +24689,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -55733,7 +55735,9 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public boolean isAllCaps();
     method public boolean isCursorVisible();
+    method public boolean isElegantTextHeight();
     method public boolean isInputMethodTarget();
     method public boolean isSuggestionsEnabled();
     method public boolean isTextSelectable();
diff --git a/api/test-current.txt b/api/test-current.txt
index efe22a7..c5caf39 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13898,6 +13898,7 @@
   public class Typeface {
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+    method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
     method public static android.graphics.Typeface createFromFile(java.io.File);
     method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -22878,6 +22879,7 @@
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -52107,7 +52109,9 @@
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
     method public boolean hasSelection();
+    method public boolean isAllCaps();
     method public boolean isCursorVisible();
+    method public boolean isElegantTextHeight();
     method public boolean isInputMethodTarget();
     method public boolean isSuggestionsEnabled();
     method public boolean isTextSelectable();
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index c6d83f5..b69ef1c 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -152,7 +152,7 @@
                 System.out.println(pretty);
             } else {
                 if (results != null) {
-                    for (String key : results.keySet()) {
+                    for (String key : sorted(results.keySet())) {
                         System.out.println(
                                 "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
                     }
@@ -163,6 +163,10 @@
 
         @Override
         public void onError(String errorText, boolean commandError) {
+            if (mRawMode) {
+                System.out.println("onError: commandError=" + commandError + " message="
+                        + errorText);
+            }
             // The regular BaseCommand error printing will print the commandErrors.
             if (!commandError) {
                 System.out.println(errorText);
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 3ea823c..0532083 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -10,7 +10,7 @@
 
     srcs: [
         "IncidentHelper.cpp",
-        "strutil.cpp",
+        "ih_util.cpp",
     ],
 
     shared_libs: [
@@ -38,6 +38,7 @@
 
     srcs: [
         "tests/IncidentHelper_test.cpp",
+        "tests/ih_util_test.cpp",
     ],
 
     data: [
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
index 89d8947..aa06595 100644
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -17,14 +17,13 @@
 #define LOG_TAG "incident_helper"
 
 #include "IncidentHelper.h"
-#include "strutil.h"
+#include "ih_util.h"
 
 #include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
 
-#include <algorithm>
 #include <android-base/file.h>
 #include <unistd.h>
-#include <sstream>
 #include <string>
 #include <vector>
 
@@ -67,42 +66,34 @@
 const string KERNEL_WAKEUP_LINE_DELIMITER = "\t";
 
 status_t KernelWakesParser::Parse(const int in, const int out) const {
-    // read the content, this is not memory-efficient though since it loads everything
-    // However the data will be held in proto anyway, and incident_helper is less critical
-    string content;
-    if (!ReadFdToString(in, &content)) {
-        fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
-        return -1;
-    }
-
-    istringstream iss(content);
+    Reader reader(in);
     string line;
-    vector<string> header;  // the header of /d/wakeup_sources
-    vector<string> record;  // retain each record
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
     int nline = 0;
 
     KernelWakeSources proto;
 
     // parse line by line
-    while (getline(iss, line)) {
+    while (reader.readLine(line)) {
+        if (line.empty()) continue;
         // parse head line
-        if (nline == 0) {
-          split(line, &header);
-          if (!assertHeaders(kernel_wake_headers, header)) {
-            fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
-            return BAD_VALUE;
-          }
-          nline++;
-          continue;
+        if (nline++ == 0) {
+            split(line, header, KERNEL_WAKEUP_LINE_DELIMITER);
+            if (!assertHeaders(kernel_wake_headers, header)) {
+                fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+                return BAD_VALUE;
+            }
+            continue;
         }
 
         // parse for each record, the line delimiter is \t only!
-        split(line, &record, KERNEL_WAKEUP_LINE_DELIMITER);
+        split(line, record, KERNEL_WAKEUP_LINE_DELIMITER);
 
         if (record.size() != header.size()) {
-          // TODO: log this to incident report!
-          fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
-          continue;
+            // TODO: log this to incident report!
+            fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline, line.c_str());
+            continue;
         }
 
         WakeupSourceProto* source = proto.add_wakeup_sources();
@@ -118,17 +109,106 @@
         source->set_max_time(atol(record.at(7).c_str()));
         source->set_last_change(atol(record.at(8).c_str()));
         source->set_prevent_suspend_time(atol(record.at(9).c_str()));
-
-        nline++;
     }
 
-    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+    if (!reader.ok(line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
 
     if (!proto.SerializeToFileDescriptor(out)) {
         fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
         return -1;
     }
-    close(out);
-
+    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
     return NO_ERROR;
 }
+
+// ================================================================================
+const char* procrank_headers[] = {
+    "PID",          // id:  1
+    "Vss",          // id:  2
+    "Rss",          // id:  3
+    "Pss",          // id:  4
+    "Uss",          // id:  5
+    "Swap",         // id:  6
+    "PSwap",        // id:  7
+    "USwap",        // id:  8
+    "ZSwap",        // id:  9
+    "cmdline",      // id: 10
+};
+
+status_t ProcrankParser::Parse(const int in, const int out) const {
+    Reader reader(in);
+    string line, content;
+    header_t header;  // the header of /d/wakeup_sources
+    record_t record;  // retain each record
+    int nline = 0;
+
+    Procrank proto;
+
+    // parse line by line
+    while (reader.readLine(line)) {
+        if (line.empty()) continue;
+
+        // parse head line
+        if (nline++ == 0) {
+            split(line, header);
+            if (!assertHeaders(procrank_headers, header)) {
+                fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+                return BAD_VALUE;
+            }
+            continue;
+        }
+
+        split(line, record);
+        if (record.size() != header.size()) {
+            if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+                ProcessProto* total = proto.mutable_summary()->mutable_total();
+                total->set_pss(atol(record.at(0).substr(0, record.at(0).size() - 1).c_str()));
+                total->set_uss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+                total->set_swap(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+                total->set_pswap(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+                total->set_uswap(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+                total->set_zswap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+            } else if (record[0] == "ZRAM:") {
+                split(line, record, ":");
+                proto.mutable_summary()->mutable_zram()->set_raw_text(record[1]);
+            } else if (record[0] == "RAM:") {
+                split(line, record, ":");
+                proto.mutable_summary()->mutable_ram()->set_raw_text(record[1]);
+            } else {
+                fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+                    line.c_str());
+            }
+            continue;
+        }
+
+        ProcessProto* process = proto.add_processes();
+        // int32
+        process->set_pid(atoi(record.at(0).c_str()));
+        // int64, remove 'K' at the end
+        process->set_vss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+        process->set_rss(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+        process->set_pss(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+        process->set_uss(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+        process->set_swap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+        process->set_pswap(atol(record.at(6).substr(0, record.at(6).size() - 1).c_str()));
+        process->set_uswap(atol(record.at(7).substr(0, record.at(7).size() - 1).c_str()));
+        process->set_zswap(atol(record.at(8).substr(0, record.at(8).size() - 1).c_str()));
+        // string
+        process->set_cmdline(record.at(9));
+    }
+
+    if (!reader.ok(line)) {
+        fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+        return -1;
+    }
+
+    if (!proto.SerializeToFileDescriptor(out)) {
+        fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+        return -1;
+    }
+    fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+    return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
index 40792a8..736f848 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -70,4 +70,17 @@
     virtual status_t Parse(const int in, const int out) const;
 };
 
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+extern const char* procrank_headers[];
+
+class ProcrankParser : public TextParserBase {
+public:
+    ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+    ~ProcrankParser() {};
+
+    virtual status_t Parse(const int in, const int out) const;
+};
+
 #endif  // INCIDENT_HELPER_H
diff --git a/cmds/incident_helper/README.md b/cmds/incident_helper/README.md
index 967f1e3..866cc20 100644
--- a/cmds/incident_helper/README.md
+++ b/cmds/incident_helper/README.md
@@ -1,5 +1,7 @@
 # incident_helper
 
+It is an executable used to help parsing text format data to protobuf.
+
 ## How to build, deploy, unit test
 
 For the first time, build the test and create an empty directly on device:
@@ -15,3 +17,10 @@
 adb push $OUT/data/nativetest64/incident_helper_test/* /data/nativetest64/incident_helper_test/ && \
 adb shell /data/nativetest64/incident_helper_test/incident_helper_test 2>/dev/null
 ```
+## How to adapt proto changes
+
+If add a new proto file, add it in Android.bp under frameworks/base/ and make incident helper
+
+```
+root$ make -j48 incident_helper
+```
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/ih_util.cpp
new file mode 100644
index 0000000..bbb625f
--- /dev/null
+++ b/cmds/incident_helper/ih_util.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.
+ */
+
+#define LOG_TAG "incident_helper"
+
+#include "ih_util.h"
+
+#include <sstream>
+#include <unistd.h>
+
+const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB
+
+std::string trim(const std::string& s, const std::string& whitespace) {
+    const auto head = s.find_first_not_of(whitespace);
+    if (head == std::string::npos) return "";
+
+    const auto tail = s.find_last_not_of(whitespace);
+    return s.substr(head, tail - head + 1);
+}
+
+// This is similiar to Split in android-base/file.h, but it won't add empty string
+void split(const std::string& line, std::vector<std::string>& words, const std::string& delimiters) {
+    words.clear();  // clear the buffer before split
+
+    size_t base = 0;
+    size_t found;
+    while (true) {
+        found = line.find_first_of(delimiters, base);
+        if (found != base) {
+            std::string word = trim(line.substr(base, found - base));
+            if (!word.empty()) {
+                words.push_back(word);
+            }
+        }
+        if (found == line.npos) break;
+        base = found + 1;
+    }
+}
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
+    for (size_t i = 0; i < actual.size(); i++) {
+        if (expected[i] == NULL || std::string(expected[i]) != actual[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
+
+Reader::Reader(const int fd, const size_t capacity)
+        : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
+{
+    mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
+    mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
+}
+
+Reader::~Reader()
+{
+    free(mBuf);
+}
+
+bool Reader::readLine(std::string& line, const char newline) {
+    if (!ok(line)) return false; // bad status
+    std::stringstream ss;
+    while (!EOR()) {
+        // read if available
+        if (mFd != -1 && mBufSize != mMaxSize) {
+            ssize_t amt = 0;
+            if (mRead >= mFlushed) {
+                amt = ::read(mFd, mBuf + mRead, mMaxSize - mRead);
+            } else {
+                amt = ::read(mFd, mBuf + mRead, mFlushed - mRead);
+            }
+            if (amt < 0) {
+                mStatus = "Fail to read from fd";
+                return false;
+            } else if (amt == 0) {
+                close(mFd);
+                mFd = -1;
+            }
+            mRead += amt;
+            mBufSize += amt;
+        }
+
+        bool meetsNewLine = false;
+        if (mBufSize > 0) {
+            int start = mFlushed;
+            int end = mFlushed < mRead ? mRead : mMaxSize;
+            while (mFlushed < end && mBuf[mFlushed++] != newline && mBufSize > 0) mBufSize--;
+            meetsNewLine = (mBuf[mFlushed-1] == newline);
+            if (meetsNewLine) mBufSize--; // deduct the new line character
+            size_t len = meetsNewLine ? mFlushed - start - 1 : mFlushed - start;
+            ss.write(mBuf + start, len);
+        }
+
+        if (mRead >= (int) mMaxSize) mRead = 0;
+        if (mFlushed >= (int) mMaxSize) mFlushed = 0;
+
+        if (EOR() || meetsNewLine) {
+            line.assign(ss.str());
+            return true;
+        }
+    }
+    return false;
+}
+
+bool Reader::ok(std::string& error) {
+    error.assign(mStatus);
+    return mStatus.empty();
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/ih_util.h
new file mode 100644
index 0000000..9e0c18e
--- /dev/null
+++ b/cmds/incident_helper/ih_util.h
@@ -0,0 +1,64 @@
+/*
+ * 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 INCIDENT_HELPER_UTIL_H
+#define INCIDENT_HELPER_UTIL_H
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+typedef std::vector<std::string> header_t;
+typedef std::vector<std::string> record_t;
+
+const char DEFAULT_NEWLINE = '\n';
+const std::string DEFAULT_WHITESPACE = " \t";
+
+std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
+
+void split(const std::string& line, std::vector<std::string>& words,
+    const std::string& delimiters = DEFAULT_WHITESPACE);
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
+
+/**
+ * Reader class reads data from given fd in streaming fashion.
+ * The buffer size is controlled by capacity parameter.
+ */
+class Reader
+{
+public:
+    Reader(const int fd);
+    Reader(const int fd, const size_t capacity);
+    ~Reader();
+
+    bool readLine(std::string& line, const char newline = DEFAULT_NEWLINE);
+    bool ok(std::string& error);
+
+private:
+    int mFd; // set mFd to -1 when read EOF()
+    const size_t mMaxSize;
+    size_t mBufSize;
+    char* mBuf; // implements a circular buffer
+
+    int mRead;
+    int mFlushed;
+    std::string mStatus;
+    // end of read
+    inline bool EOR() { return mFd == -1 && mBufSize == 0; };
+};
+
+#endif  // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
index 1e25c85..333344b 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/main.cpp
@@ -27,12 +27,11 @@
 using namespace std;
 
 static void usage(FILE* out) {
-    fprintf(out, "incident_helper is not designed to run manually, see README.md\n");
-    fprintf(out, "usage: incident_helper -s SECTION -i INPUT -o OUTPUT\n");
+    fprintf(out, "incident_helper is not designed to run manually,");
+    fprintf(out, "it reads from stdin and writes to stdout, see README.md for details.\n");
+    fprintf(out, "usage: incident_helper -s SECTION\n");
     fprintf(out, "REQUIRED:\n");
     fprintf(out, "  -s           section id, must be positive\n");
-    fprintf(out, "  -i           (default stdin) input fd\n");
-    fprintf(out, "  -o           (default stdout) output fd\n");
 }
 
 //=============================================================================
@@ -45,6 +44,8 @@
             return new ReverseParser();
 /* ========================================================================= */
         // IDs larger than 0 are reserved in incident.proto
+        case 2000:
+            return new ProcrankParser();
         case 2002:
             return new KernelWakesParser();
         default:
@@ -59,9 +60,7 @@
     // Parse the args
     int opt;
     int sectionID = 0;
-    int inputFd = STDIN_FILENO;
-    int outputFd = STDOUT_FILENO;
-    while ((opt = getopt(argc, argv, "hs:i:o:")) != -1) {
+    while ((opt = getopt(argc, argv, "hs:")) != -1) {
         switch (opt) {
             case 'h':
                 usage(stdout);
@@ -69,30 +68,14 @@
             case 's':
                 sectionID = atoi(optarg);
                 break;
-            case 'i':
-                inputFd = atoi(optarg);
-                break;
-            case 'o':
-                outputFd = atoi(optarg);
-                break;
         }
     }
 
-    // Check mandatory parameters:
-    if (inputFd < 0) {
-        fprintf(stderr, "invalid input fd: %d\n", inputFd);
-        return 1;
-    }
-    if (outputFd < 0) {
-        fprintf(stderr, "invalid output fd: %d\n", outputFd);
-        return 1;
-    }
-
     fprintf(stderr, "Pasring section %d...\n", sectionID);
     TextParserBase* parser = selectParser(sectionID);
     if (parser != NULL) {
         fprintf(stderr, "Running parser: %s\n", parser->name.string());
-        status_t err = parser->Parse(inputFd, outputFd);
+        status_t err = parser->Parse(STDIN_FILENO, STDOUT_FILENO);
         if (err != NO_ERROR) {
             fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err));
             return -1;
diff --git a/cmds/incident_helper/strutil.cpp b/cmds/incident_helper/strutil.cpp
deleted file mode 100644
index 21b04a1..0000000
--- a/cmds/incident_helper/strutil.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "incident_helper"
-
-#include "strutil.h"
-
-#include <sstream>
-
-std::string trim(const std::string& s, const std::string& whitespace) {
-    const auto head = s.find_first_not_of(whitespace);
-    if (head == std::string::npos) return "";
-
-    const auto tail = s.find_last_not_of(whitespace);
-    return s.substr(head, tail - head + 1);
-}
-
-// This is similiar to Split in android-base/file.h, but it won't add empty string
-void split(const std::string& line, std::vector<std::string>* words, const std::string& delimiters) {
-    words->clear();  // clear the buffer before split
-
-    size_t base = 0;
-    size_t found;
-    while (true) {
-        found = line.find_first_of(delimiters, base);
-        if (found != base) { // ignore empty string
-            // one char before found
-            words->push_back(line.substr(base, found - base));
-        }
-        if (found == line.npos) break;
-        base = found + 1;
-    }
-}
-
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
-    for (size_t i = 0; i < actual.size(); i++) {
-        if (expected[i] == NULL || std::string(expected[i]) != actual.at(i)) {
-            return false;
-        }
-    }
-    return true;
-}
diff --git a/cmds/incident_helper/strutil.h b/cmds/incident_helper/strutil.h
deleted file mode 100644
index fcc164d..0000000
--- a/cmds/incident_helper/strutil.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 STRUTIL_H
-#define STRUTIL_H
-
-#include <string>
-#include <vector>
-
-const std::string DEFAULT_WHITESPACE = " \t";
-
-std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
-void split(const std::string& line, std::vector<std::string>* words,
-    const std::string& delimiters = DEFAULT_WHITESPACE);
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
-
-#endif  // STRUTIL_H
diff --git a/cmds/incident_helper/testdata/procrank.txt b/cmds/incident_helper/testdata/procrank.txt
new file mode 100644
index 0000000..5d2d8d2
--- /dev/null
+++ b/cmds/incident_helper/testdata/procrank.txt
@@ -0,0 +1,8 @@
+  PID       Vss      Rss      Pss      Uss     Swap    PSwap    USwap    ZSwap  cmdline
+ 1119  2607640K  339564K  180278K  114216K    1584K      46K       0K      10K  system_server
+  649    11016K    1448K      98K      48K     472K     342K     212K      75K  /vendor/bin/qseecomd
+                           ------   ------   ------   ------   ------   ------  ------
+                          1201993K  935300K   88164K   31069K   27612K    6826K  TOTAL
+
+ZRAM: 6828K physical used for 31076K in swap (524284K total swap)
+ RAM: 3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
index 9d5d9e4..ac3e84d 100644
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ b/cmds/incident_helper/tests/IncidentHelper_test.cpp
@@ -17,10 +17,12 @@
 #include "IncidentHelper.h"
 
 #include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
 
 #include <android-base/file.h>
 #include <android-base/test_utils.h>
 #include <gmock/gmock.h>
+#include <google/protobuf/message.h>
 #include <gtest/gtest.h>
 #include <string.h>
 #include <fcntl.h>
@@ -37,10 +39,19 @@
 class IncidentHelperTest : public Test {
 public:
     virtual void SetUp() override {
+        ASSERT_TRUE(tf.fd != -1);
+    }
 
+    std::string getSerializedString(::google::protobuf::Message& message) {
+        std::string expectedStr;
+        message.SerializeToFileDescriptor(tf.fd);
+        ReadFileToString(tf.path, &expectedStr);
+        return expectedStr;
     }
 
 protected:
+    TemporaryFile tf;
+
     const std::string kTestPath = GetExecutableDirectory();
     const std::string kTestDataPath = kTestPath + "/testdata/";
 };
@@ -61,9 +72,6 @@
     const std::string testFile = kTestDataPath + "kernel_wakeups.txt";
     KernelWakesParser parser;
     KernelWakeSources expected;
-    std::string expectedStr;
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
 
     WakeupSourceProto* record1 = expected.add_wakeup_sources();
     record1->set_name("ipc000000ab_ATFWD-daemon");
@@ -89,15 +97,12 @@
     record2->set_last_change(2067286206l);
     record2->set_prevent_suspend_time(0l);
 
-    ASSERT_TRUE(expected.SerializeToFileDescriptor(tf.fd));
-    ASSERT_TRUE(ReadFileToString(tf.path, &expectedStr));
-
     int fd = open(testFile.c_str(), O_RDONLY, 0444);
     ASSERT_TRUE(fd != -1);
 
     CaptureStdout();
     ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
-    EXPECT_EQ(GetCapturedStdout(), expectedStr);
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
     close(fd);
 }
 
@@ -115,3 +120,54 @@
     EXPECT_THAT(GetCapturedStderr(), StrEq("[KernelWakeSources]Bad header:\nTHIS IS BAD HEADER\n"));
     close(fd);
 }
+
+TEST_F(IncidentHelperTest, ProcrankParser) {
+    const std::string testFile = kTestDataPath + "procrank.txt";
+    ProcrankParser parser;
+    Procrank expected;
+
+    ProcessProto* process1 = expected.add_processes();
+    process1->set_pid(1119);
+    process1->set_vss(2607640);
+    process1->set_rss(339564);
+    process1->set_pss(180278);
+    process1->set_uss(114216);
+    process1->set_swap(1584);
+    process1->set_pswap(46);
+    process1->set_uswap(0);
+    process1->set_zswap(10);
+    process1->set_cmdline("system_server");
+
+    ProcessProto* process2 = expected.add_processes();
+    process2->set_pid(649);
+    process2->set_vss(11016);
+    process2->set_rss(1448);
+    process2->set_pss(98);
+    process2->set_uss(48);
+    process2->set_swap(472);
+    process2->set_pswap(342);
+    process2->set_uswap(212);
+    process2->set_zswap(75);
+    process2->set_cmdline("/vendor/bin/qseecomd");
+
+    ProcessProto* total = expected.mutable_summary()->mutable_total();
+    total->set_pss(1201993);
+    total->set_uss(935300);
+    total->set_swap(88164);
+    total->set_pswap(31069);
+    total->set_uswap(27612);
+    total->set_zswap(6826);
+
+    expected.mutable_summary()->mutable_zram()
+        ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+    expected.mutable_summary()->mutable_ram()
+        ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+    int fd = open(testFile.c_str(), O_RDONLY, 0444);
+    ASSERT_TRUE(fd != -1);
+
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+    EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+    close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
new file mode 100644
index 0000000..5158e0a
--- /dev/null
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "ih_util.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+
+TEST(IhUtilTest, Trim) {
+    EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw  "), StrEq("100 00\toooh \t wqrw"));
+    EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw  ", " "), StrEq("\t 100 00\toooh \t wqrw"));
+}
+
+TEST(IhUtilTest, Split) {
+    vector<string> result, expected;
+    split(" \t \t\t ", result);
+    EXPECT_EQ(expected, result);
+
+    split(" \t 100 00\toooh \t wqrw", result);
+    expected = { "100", "00", "oooh", "wqrw" };
+    EXPECT_EQ(expected, result);
+
+    split(" \t 100 00\toooh \t wqrw", result, "\t");
+    expected = { "100 00", "oooh", "wqrw" };
+    EXPECT_EQ(expected, result);
+
+    split("123,456,78_9", result, ",");
+    expected = { "123", "456", "78_9" };
+    EXPECT_EQ(expected, result);
+}
+
+TEST(IhUtilTest, Reader) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("test string"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("second"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("ooo"));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderSmallBufSize) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
+
+    Reader r(tf.fd, 5);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("test string"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("second"));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq("ooiecccojreo"));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderEmpty) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_FALSE(r.readLine(line));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderMultipleEmptyLines) {
+    TemporaryFile tf;
+    ASSERT_NE(tf.fd, -1);
+    ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+
+    Reader r(tf.fd);
+    string line;
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_TRUE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_FALSE(r.readLine(line));
+    EXPECT_THAT(line, StrEq(""));
+    ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderFailedNegativeFd) {
+    Reader r(-123);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Negative fd"));
+}
+
+TEST(IhUtilTest, ReaderFailedZeroBufferSize) {
+    Reader r(23, 0);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Zero buffer capacity"));
+}
+
+TEST(IhUtilTest, ReaderFailedBadFd) {
+    Reader r(1231432);
+    string line;
+    EXPECT_FALSE(r.readLine(line));
+    EXPECT_FALSE(r.ok(line));
+    EXPECT_THAT(line, StrEq("Fail to read from fd"));
+}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index a8a5483..537c910 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -79,7 +79,10 @@
     src/Reporter.cpp \
     src/Section.cpp \
     src/protobuf.cpp \
+    src/report_directory.cpp \
+    src/section_list.cpp \
     tests/FdBuffer_test.cpp \
+    tests/Reporter_test.cpp \
     tests/Section_test.cpp \
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
index d11e3cf..250a7f3 100644
--- a/cmds/incidentd/incidentd.rc
+++ b/cmds/incidentd/incidentd.rc
@@ -12,5 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-#service incidentd /system/bin/incidentd
+# service incidentd /system/bin/incidentd
 #    class main
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 7888442..03a6d18 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -98,7 +98,7 @@
     bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
     ~Fpipe() { close(); }
 
-    inline status_t init() { return pipe(mFds); }
+    inline bool init() { return pipe(mFds) != -1; }
     inline int readFd() const { return mFds[0]; }
     inline int writeFd() const { return mFds[1]; }
 
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 7c6789e..c4b54bb 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -153,7 +153,6 @@
             break;
         }
         reporter->batch.add(request);
-        reporter->args.merge(request->args);
     }
 
     // Take the report, which might take a while. More requests might queue
@@ -235,7 +234,7 @@
         return Status::fromExceptionCode(Status::EX_SECURITY,
                 "Only system uid can call systemRunning");
     }
-    
+
     // When system_server is up and running, schedule the dropbox task to run.
     mHandler->scheduleSendBacklogToDropbox();
 
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index ba157de6..ea73bcd 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,7 +35,7 @@
 /**
  * The directory where the incident reports are stored.
  */
-static const String8 INCIDENT_DIRECTORY("/data/incidents");
+static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
 
 // ================================================================================
 static status_t write_all(int fd, uint8_t const* buf, size_t size)
@@ -68,6 +68,7 @@
 // ================================================================================
 ReportRequestSet::ReportRequestSet()
     :mRequests(),
+     mSections(),
      mWritableCount(0),
      mMainFd(-1)
 {
@@ -77,10 +78,12 @@
 {
 }
 
+// TODO: dedup on exact same args and fd, report the status back to listener!
 void
 ReportRequestSet::add(const sp<ReportRequest>& request)
 {
     mRequests.push_back(request);
+    mSections.merge(request->args);
     mWritableCount++;
 }
 
@@ -122,11 +125,16 @@
     return mWritableCount > 0 ? NO_ERROR : err;
 }
 
+bool
+ReportRequestSet::containsSection(int id) {
+    return mSections.containsSection(id);
+}
 
 // ================================================================================
-Reporter::Reporter()
-    :args(),
-     batch()
+Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
+
+Reporter::Reporter(const char* directory)
+    :batch()
 {
     char buf[100];
 
@@ -134,10 +142,15 @@
     mMaxSize = 100 * 1024 * 1024;
     mMaxCount = 100;
 
+    // string ends up with '/' is a directory
+    String8 dir = String8(directory);
+    if (directory[dir.size() - 1] != '/') dir += "/";
+    mIncidentDirectory = dir.string();
+
     // There can't be two at the same time because it's on one thread.
     mStartTime = time(NULL);
-    strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
-    mFilename = INCIDENT_DIRECTORY + buf;
+    strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
+    mFilename = mIncidentDirectory + buf;
 }
 
 Reporter::~Reporter()
@@ -161,7 +174,7 @@
     }
     if (needMainFd) {
         // Create the directory
-        err = create_directory(INCIDENT_DIRECTORY);
+        if (!isTest) err = create_directory(mIncidentDirectory);
         if (err != NO_ERROR) {
             goto done;
         }
@@ -169,7 +182,7 @@
         // If there are too many files in the directory (for whatever reason),
         // delete the oldest ones until it's under the limit. Doing this first
         // does mean that we can go over, so the max size is not a hard limit.
-        clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount);
+        if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount);
 
         // Open the file.
         err = create_file(&mainFd);
@@ -214,7 +227,7 @@
         const int id = (*section)->id;
         ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string());
 
-        if (this->args.containsSection(id)) {
+        if (this->batch.containsSection(id)) {
             // Notify listener of starting
             for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
                 if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
@@ -270,7 +283,7 @@
         // If the status was ok, delete the file. If not, leave it around until the next
         // boot or the next checkin. If the directory gets too big older files will
         // be rotated out.
-        unlink(mFilename.c_str());
+        if(!isTest) unlink(mFilename.c_str());
     }
 
     return REPORT_FINISHED;
@@ -284,7 +297,7 @@
 {
     const char* filename = mFilename.c_str();
 
-    *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660);
+    *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
     if (*fd < 0) {
         ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
         return -errno;
@@ -303,20 +316,24 @@
     return NO_ERROR;
 }
 
-// ================================================================================
 Reporter::run_report_status_t
 Reporter::upload_backlog()
 {
     DIR* dir;
     struct dirent* entry;
     struct stat st;
+    status_t err;
 
-    if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) {
-        ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string());
+    if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) {
+        ALOGE("directory doesn't exist: %s", strerror(-err));
+        return REPORT_FINISHED;
+    }
+
+    if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) {
+        ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY);
         return REPORT_NEEDS_DROPBOX;
     }
 
-    String8 dirbase(INCIDENT_DIRECTORY + "/");
     sp<DropBoxManager> dropbox = new DropBoxManager();
 
     // Enumerate, count and add up size
@@ -324,7 +341,7 @@
         if (entry->d_name[0] == '.') {
             continue;
         }
-        String8 filename = dirbase + entry->d_name;
+        String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name;
         if (stat(filename.string(), &st) != 0) {
             ALOGE("Unable to stat file %s", filename.string());
             continue;
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 5b86561..509611c 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -62,8 +62,10 @@
     iterator begin() { return mRequests.begin(); }
     iterator end() { return mRequests.end(); }
 
+    bool containsSection(int id);
 private:
     vector<sp<ReportRequest>> mRequests;
+    IncidentReportArgs mSections;
     int mWritableCount;
     int mMainFd;
 };
@@ -77,10 +79,10 @@
         REPORT_NEEDS_DROPBOX = 1
     };
 
-    IncidentReportArgs args;
     ReportRequestSet batch;
 
     Reporter();
+    Reporter(const char* directory);
     virtual ~Reporter();
 
     // Run the report as described in the batch and args parameters.
@@ -89,12 +91,16 @@
     static run_report_status_t upload_backlog();
 
 private:
+    String8 mIncidentDirectory;
+
     string mFilename;
     off_t mMaxSize;
     size_t mMaxCount;
     time_t mStartTime;
 
     status_t create_file(int* fd);
+
+    bool isTest = true; // default to true for testing
 };
 
 
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index ddb54c1..9cc47ed 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -19,6 +19,7 @@
 #include "Section.h"
 #include "protobuf.h"
 
+#include <private/android_filesystem_config.h>
 #include <binder/IServiceManager.h>
 #include <mutex>
 #include <stdio.h>
@@ -29,15 +30,68 @@
 
 using namespace std;
 
-const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
-const int64_t INCIDENT_HELPER_TIMEOUT_MS = 5 * 1000;  // 5 seconds
+const int   WAIT_MAX = 5;
+const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
 const char* INCIDENT_HELPER = "/system/bin/incident_helper";
-const uid_t IH_UID = 9999;  // run incident_helper as nobody
-const gid_t IH_GID = 9999;
+
+static pid_t
+forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
+{
+    const char* ihArgs[] { INCIDENT_HELPER, "-s", to_string(id).c_str(), NULL };
+
+    // fork used in multithreaded environment, avoid adding unnecessary code in child process
+    pid_t pid = fork();
+    if (pid == 0) {
+        // child process executes incident helper as nobody
+        if (setgid(AID_NOBODY) == -1) {
+            ALOGW("%s can't change gid: %s", name, strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+        if (setuid(AID_NOBODY) == -1) {
+            ALOGW("%s can't change uid: %s", name, strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+
+        if (dup2(p2cPipe.readFd(),  STDIN_FILENO)  != 0 || !p2cPipe.close() ||
+            dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
+            ALOGW("%s can't setup stdin and stdout for incident helper", name);
+            _exit(EXIT_FAILURE);
+        }
+
+        execv(INCIDENT_HELPER, const_cast<char**>(ihArgs));
+
+        ALOGW("%s failed in incident helper process: %s", name, strerror(errno));
+        _exit(EXIT_FAILURE); // always exits with failure if any
+    }
+    // close the fds used in incident helper
+    close(p2cPipe.readFd());
+    close(c2pPipe.writeFd());
+    return pid;
+}
+
+static status_t killChild(pid_t pid) {
+    int status;
+    kill(pid, SIGKILL);
+    if (waitpid(pid, &status, 0) == -1) return -1;
+    return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
+
+static status_t waitForChild(pid_t pid) {
+    int status;
+    bool died = false;
+    // wait for child to report status up to 1 seconds
+    for(int loop = 0; !died && loop < WAIT_MAX; loop++) {
+        if (waitpid(pid, &status, WNOHANG) == pid) died = true;
+        // sleep for 0.2 second
+        nanosleep(&WAIT_INTERVAL_NS, NULL);
+    }
+    if (!died) return killChild(pid);
+    return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
 
 // ================================================================================
-Section::Section(int i)
-    :id(i)
+Section::Section(int i, const int64_t timeoutMs)
+    :id(i), timeoutMs(timeoutMs)
 {
 }
 
@@ -55,106 +109,57 @@
 }
 
 // ================================================================================
-FileSection::FileSection(int id, const char* filename)
-        : Section(id), mFilename(filename) {
-    name = "cat ";
-    name += filename;
+FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
+        : Section(id, timeoutMs), mFilename(filename) {
+    name = filename;
 }
 
 FileSection::~FileSection() {}
 
 status_t FileSection::Execute(ReportRequestSet* requests) const {
-    Fpipe p2cPipe;
-    Fpipe c2pPipe;
-    FdBuffer buffer;
-
-    // initiate pipes to pass data to/from incident_helper
-    if (p2cPipe.init() == -1) {
-        return -errno;
-    }
-    if (c2pPipe.init() == -1) {
-        return -errno;
-    }
-
-    // fork a child process
-    pid_t pid = fork();
-
-    if (pid == -1) {
-        ALOGW("FileSection '%s' failed to fork", this->name.string());
-        return -errno;
-    }
-
-    // child process
-    if (pid == 0) {
-        if (setgid(IH_GID) == -1) {
-            ALOGW("FileSection '%s' can't change gid: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        if (setuid(IH_UID) == -1) {
-            ALOGW("FileSection '%s' can't change uid: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-
-        if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close()) {
-            ALOGW("FileSection '%s' failed to set up stdin: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        if (dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
-            ALOGW("FileSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-
-        // execute incident_helper to parse raw file data and generate protobuf
-        char sectionID[8];  // section id is expected to be smaller than 8 digits
-        sprintf(sectionID, "%d", this->id);
-        const char* args[]{INCIDENT_HELPER, "-s", sectionID, NULL};
-        execv(INCIDENT_HELPER, const_cast<char**>(args));
-
-        ALOGW("FileSection '%s' failed in child process: %s", this->name.string(), strerror(errno));
-        return -1;
-    }
-
-    // parent process
-
-    // close fds used in child process
-    close(p2cPipe.readFd());
-    close(c2pPipe.writeFd());
-
-    // read from mFilename and pump buffer to incident_helper
-    status_t err = NO_ERROR;
-    int fd = open(mFilename, O_RDONLY);
+    // read from mFilename first, make sure the file is available
+    // add O_CLOEXEC to make sure it is closed when exec incident helper
+    int fd = open(mFilename, O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
        ALOGW("FileSection '%s' failed to open file", this->name.string());
        return -errno;
     }
 
-    err = buffer.readProcessedDataInStream(fd,
-        p2cPipe.writeFd(), c2pPipe.readFd(), INCIDENT_HELPER_TIMEOUT_MS);
-    if (err != NO_ERROR) {
-        ALOGW("FileSection '%s' failed to read data from incident helper: %s",
-            this->name.string(), strerror(-err));
-        kill(pid, SIGKILL); // kill child process if meets error
-        return err;
-    }
-
-    if (buffer.timedOut()) {
-        ALOGW("FileSection '%s' timed out reading from incident helper!", this->name.string());
-        kill(pid, SIGKILL); // kill the child process if timed out
-    }
-
-    // has to block here to reap child process
-    int status;
-    int w = waitpid(pid, &status, 0);
-    if (w < 0 || status == -1) {
-        ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-err));
+    FdBuffer buffer;
+    Fpipe p2cPipe;
+    Fpipe c2pPipe;
+    // initiate pipes to pass data to/from incident_helper
+    if (!p2cPipe.init() || !c2pPipe.init()) {
+        ALOGW("FileSection '%s' failed to setup pipes", this->name.string());
         return -errno;
     }
 
-    // write parsed data to reporter
-    ALOGD("section '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+    pid_t pid = forkAndExecuteIncidentHelper(this->id, this->name.string(), p2cPipe, c2pPipe);
+    if (pid == -1) {
+        ALOGW("FileSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+
+    // parent process
+    status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
+            this->timeoutMs);
+    if (readStatus != NO_ERROR || buffer.timedOut()) {
+        ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
+            this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+            strerror(-killChild(pid)));
+        return readStatus;
+    }
+
+    status_t ihStatus = waitForChild(pid);
+    if (ihStatus != NO_ERROR) {
+        ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus));
+        return ihStatus;
+    }
+
+    ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
             (int)buffer.durationMs());
     WriteHeader(requests, buffer.size());
-    err = buffer.write(requests);
+    status_t err = buffer.write(requests);
     if (err != NO_ERROR) {
         ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
         return err;
@@ -263,7 +268,7 @@
     pthread_attr_destroy(&attr);
 
     // Loop reading until either the timeout or the worker side is done (i.e. eof).
-    err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS);
+    err = buffer.read(data->readFd(), this->timeoutMs);
     if (err != NO_ERROR) {
         // TODO: Log this error into the incident report.
         ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(),
@@ -309,7 +314,7 @@
     }
 
     // Write the data that was collected
-    ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
+    ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
             (int)buffer.durationMs());
     WriteHeader(requests, buffer.size());
     err = buffer.write(requests);
@@ -322,42 +327,116 @@
 }
 
 // ================================================================================
-CommandSection::CommandSection(int id, const char* first, ...)
-    :Section(id)
+void CommandSection::init(const char* command, va_list args)
+{
+    va_list copied_args;
+    va_copy(copied_args, args);
+    int numOfArgs = 0;
+    while(va_arg(args, const char*) != NULL) {
+        numOfArgs++;
+    }
+
+    // allocate extra 1 for command and 1 for NULL terminator
+    mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2));
+
+    mCommand[0] = command;
+    name = command;
+    for (int i=0; i<numOfArgs; i++) {
+        const char* arg = va_arg(copied_args, const char*);
+        mCommand[i+1] = arg;
+        name += " ";
+        name += arg;
+    }
+    mCommand[numOfArgs+1] = NULL;
+    va_end(copied_args);
+}
+
+CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
+        : Section(id, timeoutMs)
 {
     va_list args;
-    int count = 0;
-
-    va_start(args, first);
-    while (va_arg(args, const char*) != NULL) {
-        count++;
-    }
+    va_start(args, command);
+    init(command, args);
     va_end(args);
+}
 
-    mCommand = (const char**)malloc(sizeof(const char*) * count);
-
-    mCommand[0] = first;
-    name = first;
-    name += " ";
-    va_start(args, first);
-    for (int i=0; i<count; i++) {
-        const char* arg = va_arg(args, const char*);
-        mCommand[i+1] = arg;
-        if (arg != NULL) {
-            name += va_arg(args, const char*);
-            name += " ";
-        }
-    }
+CommandSection::CommandSection(int id, const char* command, ...)
+        : Section(id)
+{
+    va_list args;
+    va_start(args, command);
+    init(command, args);
     va_end(args);
 }
 
 CommandSection::~CommandSection()
 {
+    free(mCommand);
 }
 
 status_t
-CommandSection::Execute(ReportRequestSet* /*requests*/) const
+CommandSection::Execute(ReportRequestSet* requests) const
 {
+    FdBuffer buffer;
+    Fpipe cmdPipe;
+    Fpipe ihPipe;
+
+    if (!cmdPipe.init() || !ihPipe.init()) {
+        ALOGW("CommandSection '%s' failed to setup pipes", this->name.string());
+        return -errno;
+    }
+
+    pid_t cmdPid = fork();
+    if (cmdPid == -1) {
+        ALOGW("CommandSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+    // child process to execute the command as root
+    if (cmdPid == 0) {
+        // replace command's stdout with ihPipe's write Fd
+        if (dup2(cmdPipe.writeFd(), STDOUT_FILENO) != 1 || !ihPipe.close() || !cmdPipe.close()) {
+            ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+        execv(this->mCommand[0], (char *const *) this->mCommand);
+        int err = errno; // record command error code
+        ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
+        _exit(err); // exit with command error code
+    }
+    pid_t ihPid = forkAndExecuteIncidentHelper(this->id, this->name.string(), cmdPipe, ihPipe);
+    if (ihPid == -1) {
+        ALOGW("CommandSection '%s' failed to fork", this->name.string());
+        return -errno;
+    }
+
+    close(cmdPipe.writeFd());
+    status_t readStatus = buffer.read(ihPipe.readFd(), this->timeoutMs);
+    if (readStatus != NO_ERROR || buffer.timedOut()) {
+        ALOGW("CommandSection '%s' failed to read data from incident helper: %s, "
+            "timedout: %s, kill command: %s, kill incident helper: %s",
+            this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+            strerror(-killChild(cmdPid)), strerror(-killChild(ihPid)));
+        return readStatus;
+    }
+
+    // TODO: wait for command here has one trade-off: the failed status of command won't be detected until
+    //       buffer timeout, but it has advatage on starting the data stream earlier.
+    status_t cmdStatus = waitForChild(cmdPid);
+    status_t ihStatus  = waitForChild(ihPid);
+    if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
+        ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s",
+            this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
+        return cmdStatus != NO_ERROR ? cmdStatus : ihStatus;
+    }
+
+    ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+            (int)buffer.durationMs());
+    WriteHeader(requests, buffer.size());
+    status_t err = buffer.write(requests);
+    if (err != NO_ERROR) {
+        ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+        return err;
+    }
     return NO_ERROR;
 }
 
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 35740e9..93b4848 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -19,22 +19,26 @@
 
 #include "FdBuffer.h"
 
+#include <stdarg.h>
 #include <utils/String8.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
 using namespace android;
 
+const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
+
 /**
  * Base class for sections
  */
 class Section
 {
 public:
-    int id;
+    const int id;
+    const int64_t timeoutMs; // each section must have a timeout
     String8 name;
 
-    Section(int id);
+    Section(int id, const int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS);
     virtual ~Section();
 
     virtual status_t Execute(ReportRequestSet* requests) const = 0;
@@ -48,7 +52,7 @@
 class FileSection : public Section
 {
 public:
-    FileSection(int id, const char* filename);
+    FileSection(int id, const char* filename, const int64_t timeoutMs = 5000 /* 5 seconds */);
     virtual ~FileSection();
 
     virtual status_t Execute(ReportRequestSet* requests) const;
@@ -77,13 +81,18 @@
 class CommandSection : public Section
 {
 public:
-    CommandSection(int id, const char* first, ...);
+    CommandSection(int id, const int64_t timeoutMs, const char* command, ...);
+
+    CommandSection(int id, const char* command, ...);
+
     virtual ~CommandSection();
 
     virtual status_t Execute(ReportRequestSet* requests) const;
 
 private:
     const char** mCommand;
+
+    void init(const char* command, va_list args);
 };
 
 /**
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index f60b8ac..110902c 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -129,7 +129,8 @@
         return;
     }
 
-    String8 dirbase(String8(directory) + "/");
+    String8 dirbase(directory);
+    if (directory[dirbase.size() - 1] != '/') dirbase += "/";
 
     off_t totalSize = 0;
     size_t totalCount = 0;
diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp
index 72c54d2..80ddb86 100644
--- a/cmds/incidentd/src/section_list.cpp
+++ b/cmds/incidentd/src/section_list.cpp
@@ -21,6 +21,7 @@
  */
 const Section* SECTION_LIST[] = {
     // Linux Services
+    new CommandSection(2000, "/system/xbin/procrank", NULL),
     new FileSection(2002, "/d/wakeup_sources"),
 
     // System Services
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index ca5eec8..ba8b77a 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -25,6 +25,7 @@
 
 const int READ_TIMEOUT = 5 * 1000;
 const int BUFFER_SIZE = 16 * 1024;
+const int QUICK_TIMEOUT_MS = 100;
 const std::string HEAD = "[OK]";
 
 using namespace android;
@@ -101,11 +102,11 @@
             write(c2pPipe.writeFd(), "poo", 3);
             sleep(1);
         }
-        exit(EXIT_FAILURE);
+        _exit(EXIT_FAILURE);
     } else {
         close(c2pPipe.writeFd());
 
-        status_t status = buffer.read(c2pPipe.readFd(), 500);
+        status_t status = buffer.read(c2pPipe.readFd(), QUICK_TIMEOUT_MS);
         ASSERT_EQ(NO_ERROR, status);
         EXPECT_TRUE(buffer.timedOut());
 
@@ -129,7 +130,7 @@
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
         // Must exit here otherwise the child process will continue executing the test binary.
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -161,7 +162,7 @@
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
         // Must exit here otherwise the child process will continue executing the test binary.
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -186,7 +187,7 @@
         ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -212,7 +213,7 @@
         ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
-        exit(EXIT_SUCCESS);
+        _exit(EXIT_SUCCESS);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
@@ -239,13 +240,13 @@
         while (true) {
             sleep(1);
         }
-        exit(EXIT_FAILURE);
+        _exit(EXIT_FAILURE);
     } else {
         close(p2cPipe.readFd());
         close(c2pPipe.writeFd());
 
         ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(tf.fd,
-            p2cPipe.writeFd(), c2pPipe.readFd(), 100));
+            p2cPipe.writeFd(), c2pPipe.readFd(), QUICK_TIMEOUT_MS));
         EXPECT_TRUE(buffer.timedOut());
         kill(pid, SIGKILL); // reap the child process
     }
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
new file mode 100644
index 0000000..a774741
--- /dev/null
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -0,0 +1,197 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#define LOG_TAG "incidentd"
+
+#include "Reporter.h"
+
+#include <android/os/BnIncidentReportStatusListener.h>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <dirent.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+class TestListener : public IIncidentReportStatusListener
+{
+public:
+    int startInvoked;
+    int finishInvoked;
+    int failedInvoked;
+    map<int, int> startSections;
+    map<int, int> finishSections;
+
+    TestListener() : startInvoked(0), finishInvoked(0), failedInvoked(0) {};
+    virtual ~TestListener() {};
+
+    virtual Status onReportStarted() {
+        startInvoked++;
+        return Status::ok();
+    };
+    virtual Status onReportSectionStatus(int section, int status) {
+        switch (status) {
+        case IIncidentReportStatusListener::STATUS_STARTING:
+            if (startSections.count(section) == 0)
+                startSections[section] = 0;
+            startSections[section] = startSections[section] + 1;
+            break;
+        case IIncidentReportStatusListener::STATUS_FINISHED:
+            if (finishSections.count(section) == 0)
+                finishSections[section] = 0;
+            finishSections[section] = finishSections[section] + 1;
+            break;
+        }
+        return Status::ok();
+    };
+    virtual Status onReportFinished() {
+        finishInvoked++;
+        return Status::ok();
+    };
+    virtual Status onReportFailed() {
+        failedInvoked++;
+        return Status::ok();
+    };
+
+protected:
+    IBinder* onAsBinder() override { return nullptr; };
+
+};
+
+class ReporterTest : public Test {
+public:
+    virtual void SetUp() {
+        reporter = new Reporter(td.path);
+        l = new TestListener();
+    }
+
+    vector<string> InspectFiles() {
+        DIR* dir;
+        struct dirent* entry;
+        vector<string> results;
+
+        string dirbase = string(td.path) + "/";
+        dir = opendir(td.path);
+
+        while ((entry = readdir(dir)) != NULL) {
+            if (entry->d_name[0] == '.') {
+                continue;
+            }
+            string filename = dirbase + entry->d_name;
+            string content;
+            ReadFileToString(filename, &content);
+            results.push_back(content);
+        }
+        return results;
+    }
+
+protected:
+    TemporaryDir td;
+    ReportRequestSet requests;
+    sp<Reporter> reporter;
+    sp<TestListener> l;
+};
+
+TEST_F(ReporterTest, IncidentReportArgs) {
+    IncidentReportArgs args1, args2;
+    args1.addSection(1);
+    args2.addSection(3);
+
+    args1.merge(args2);
+    ASSERT_TRUE(args1.containsSection(1));
+    ASSERT_FALSE(args1.containsSection(2));
+    ASSERT_TRUE(args1.containsSection(3));
+}
+
+TEST_F(ReporterTest, ReportRequestSetEmpty) {
+    requests.setMainFd(STDOUT_FILENO);
+
+    CaptureStdout();
+    requests.write((uint8_t *) "abcdef", 6);
+    EXPECT_THAT(GetCapturedStdout(), StrEq("abcdef"));
+}
+
+TEST_F(ReporterTest, WriteToStreamFdAndMainFd) {
+    TemporaryFile tf;
+    IncidentReportArgs args;
+    sp<ReportRequest> r = new ReportRequest(args, l, tf.fd);
+
+    requests.add(r);
+    requests.setMainFd(STDOUT_FILENO);
+
+    const char* data = "abcdef";
+
+    CaptureStdout();
+    requests.write((uint8_t *) data, 6);
+    EXPECT_THAT(GetCapturedStdout(), StrEq(data));
+
+    string content;
+    ASSERT_TRUE(ReadFileToString(tf.path, &content));
+    EXPECT_THAT(content, StrEq(data));
+}
+
+TEST_F(ReporterTest, RunReportEmpty) {
+    ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+    EXPECT_EQ(l->startInvoked, 0);
+    EXPECT_EQ(l->finishInvoked, 0);
+    EXPECT_TRUE(l->startSections.empty());
+    EXPECT_TRUE(l->finishSections.empty());
+    EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportWithHeaders) {
+    IncidentReportArgs args1, args2;
+    args1.addSection(1);
+    args2.addSection(2);
+    std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
+    args2.addHeader(header);
+    sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
+    sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+
+    reporter->batch.add(r1);
+    reporter->batch.add(r2);
+
+    CaptureStdout();
+    ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+    EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+    EXPECT_EQ(l->startInvoked, 2);
+    EXPECT_EQ(l->finishInvoked, 2);
+    EXPECT_TRUE(l->startSections.empty());
+    EXPECT_TRUE(l->finishSections.empty());
+    EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportToGivenDirectory) {
+    IncidentReportArgs args;
+    args.addHeader({'1', '2', '3'});
+    args.addHeader({'a', 'b', 'c', 'd'});
+    sp<ReportRequest> r = new ReportRequest(args, l, -1);
+    reporter->batch.add(r);
+
+    ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+    vector<string> results = InspectFiles();
+    ASSERT_EQ((int)results.size(), 1);
+    EXPECT_EQ(results[0], "\n\x3" "123\n\x4" "abcd");
+}
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 18322b6..93771ff 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,6 +22,8 @@
 #include <gtest/gtest.h>
 #include <string.h>
 
+const int QUICK_TIMEOUT_MS = 100;
+
 using namespace android::base;
 using namespace std;
 using ::testing::StrEq;
@@ -62,7 +64,56 @@
 TEST(SectionTest, FileSectionTimeout) {
     TemporaryFile tf;
     // id -1 is timeout parser
-    FileSection fs(-1, tf.path);
+    FileSection fs(-1, tf.path, QUICK_TIMEOUT_MS);
     ReportRequestSet requests;
     ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionConstructor) {
+    CommandSection cs1(1, "echo", "\"this is a test\"", "ooo", NULL);
+    CommandSection cs2(2, "single_command", NULL);
+    CommandSection cs3(1, 3123, "echo", "\"this is a test\"", "ooo", NULL);
+    CommandSection cs4(2, 43214, "single_command", NULL);
+
+    EXPECT_THAT(cs1.name.string(), StrEq("echo \"this is a test\" ooo"));
+    EXPECT_THAT(cs2.name.string(), StrEq("single_command"));
+    EXPECT_EQ(3123, cs3.timeoutMs);
+    EXPECT_EQ(43214, cs4.timeoutMs);
+    EXPECT_THAT(cs3.name.string(), StrEq("echo \"this is a test\" ooo"));
+    EXPECT_THAT(cs4.name.string(), StrEq("single_command"));
+}
+
+TEST(SectionTest, CommandSectionEcho) {
+    CommandSection cs(0, "/system/bin/echo", "about", NULL);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    CaptureStdout();
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+    EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\x06\ntuoba"));
+}
+
+TEST(SectionTest, CommandSectionCommandTimeout) {
+    CommandSection cs(0, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
+    ReportRequestSet requests;
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
+    CommandSection cs(-1, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
+    ReportRequestSet requests;
+    requests.setMainFd(STDOUT_FILENO);
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommand) {
+    CommandSection cs(0, "echo", "about", NULL);
+    ReportRequestSet requests;
+    ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
+    CommandSection cs(-1, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
+    ReportRequestSet requests;
+    // timeout will return first
+    ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
 }
\ No newline at end of file
diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java
index ea17890..975318e 100644
--- a/core/java/android/annotation/TargetApi.java
+++ b/core/java/android/annotation/TargetApi.java
@@ -16,6 +16,7 @@
 package android.annotation;
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.TYPE;
 
@@ -25,7 +26,7 @@
 
 /** Indicates that Lint should treat this type as targeting a given API level, no matter what the
     project target is. */
-@Target({TYPE, METHOD, CONSTRUCTOR})
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
 @Retention(RetentionPolicy.CLASS)
 public @interface TargetApi {
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d432544..a0082e4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2110,7 +2110,7 @@
      * is no longer available to third party
      * applications: the introduction of document-centric recents means
      * it can leak person information to the caller.  For backwards compatibility,
-     * it will still retu rn a small subset of its data: at least the caller's
+     * it will still return a small subset of its data: at least the caller's
      * own tasks, and possibly some other tasks
      * such as home that are known to not be sensitive.
      *
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c7eb29f..2954234 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5732,6 +5732,7 @@
         // probably end up doing the same disk access.
         Application app;
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
+        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
         try {
             // If the app is being launched for full backup or restore, bring it up in
             // a restricted environment with the base application class.
@@ -5759,17 +5760,21 @@
                     "Exception thrown in onCreate() of "
                     + data.instrumentationName + ": " + e.toString(), e);
             }
+            try {
+                mInstrumentation.callApplicationOnCreate(app);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(app, e)) {
+                    throw new RuntimeException(
+                      "Unable to create application " + app.getClass().getName()
+                      + ": " + e.toString(), e);
+                }
+            }
         } finally {
-            StrictMode.setThreadPolicy(savedPolicy);
-        }
-
-        try {
-            mInstrumentation.callApplicationOnCreate(app);
-        } catch (Exception e) {
-            if (!mInstrumentation.onException(app, e)) {
-                throw new RuntimeException(
-                    "Unable to create application " + app.getClass().getName()
-                    + ": " + e.toString(), e);
+            // If the app targets < O-MR1, or doesn't change the thread policy
+            // during startup, clobber the policy to maintain behavior of b/36951662
+            if (data.appInfo.targetSdkVersion <= Build.VERSION_CODES.O
+                    || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
+                StrictMode.setThreadPolicy(savedPolicy);
             }
         }
 
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 0e33934..49d58eb 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -144,15 +144,15 @@
      * or {@link WallpaperManager#FLAG_SYSTEM}
      * @return colors of chosen wallpaper
      */
-    WallpaperColors getWallpaperColors(int which);
+    WallpaperColors getWallpaperColors(int which, int userId);
 
     /**
      * Register a callback to receive color updates
      */
-    void registerWallpaperColorsCallback(IWallpaperManagerCallback cb);
+    void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
 
     /**
      * Unregister a callback that was receiving color updates
      */
-    void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb);
+    void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
 }
diff --git a/core/java/android/app/IWallpaperManagerCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl
index 0cfbaef..ea0ceab 100644
--- a/core/java/android/app/IWallpaperManagerCallback.aidl
+++ b/core/java/android/app/IWallpaperManagerCallback.aidl
@@ -34,6 +34,6 @@
     /**
      * Called when wallpaper colors change
      */
-    void onWallpaperColorsChanged(in WallpaperColors colors, int which);
+    void onWallpaperColorsChanged(in WallpaperColors colors, int which, int userId);
 
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8858172..34343e9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -204,7 +204,12 @@
     public static final int IMPORTANCE_NONE = 0;
 
     /**
-     * Min notification importance: only shows in the shade, below the fold.
+     * Min notification importance: only shows in the shade, below the fold.  This should
+     * not be used with {@link Service#startForeground(int, Notification) Service.startForeground}
+     * since a foreground service is supposed to be something the user cares about so it does
+     * not make semantic sense to mark its notification as minimum importance.  If you do this
+     * as of Android version {@link android.os.Build.VERSION_CODES#O}, the system will show
+     * a higher-priority notification about your app running in the background.
      */
     public static final int IMPORTANCE_MIN = 1;
 
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index d0791cf..2a8130f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -408,4 +408,13 @@
 
         return new Size(newWidth, newHeight);
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder colors = new StringBuilder();
+        for (int i = 0; i < mMainColors.size(); i++) {
+            colors.append(Integer.toHexString(mMainColors.get(i).toArgb())).append(" ");
+        }
+        return "[WallpaperColors: " + colors.toString() + "h: " + mColorHints + "]";
+    }
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 0398b47..16d0214 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -307,13 +307,14 @@
          * changes its colors.
          * @param callback Listener
          * @param handler Thread to call it from. Main thread if null.
+         * @param userId Owner of the wallpaper or UserHandle.USER_ALL
          */
         public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
-                @Nullable Handler handler) {
+                @Nullable Handler handler, int userId) {
             synchronized (this) {
                 if (!mColorCallbackRegistered) {
                     try {
-                        mService.registerWallpaperColorsCallback(this);
+                        mService.registerWallpaperColorsCallback(this, userId);
                         mColorCallbackRegistered = true;
                     } catch (RemoteException e) {
                         // Failed, service is gone
@@ -328,15 +329,17 @@
          * Stop listening to wallpaper color events.
          *
          * @param callback listener
+         * @param userId Owner of the wallpaper or UserHandle.USER_ALL
          */
-        public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
+        public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
+                int userId) {
             synchronized (this) {
                 mColorListeners.removeIf(pair -> pair.first == callback);
 
                 if (mColorListeners.size() == 0 && mColorCallbackRegistered) {
                     mColorCallbackRegistered = false;
                     try {
-                        mService.unregisterWallpaperColorsCallback(this);
+                        mService.unregisterWallpaperColorsCallback(this, userId);
                     } catch (RemoteException e) {
                         // Failed, service is gone
                         Log.w(TAG, "Can't unregister color updates", e);
@@ -346,7 +349,7 @@
         }
 
         @Override
-        public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+        public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
             synchronized (this) {
                 for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) {
                     Handler handler = listener.second;
@@ -361,21 +364,21 @@
                             stillExists = mColorListeners.contains(listener);
                         }
                         if (stillExists) {
-                            listener.first.onColorsChanged(colors, which);
+                            listener.first.onColorsChanged(colors, which, userId);
                         }
                     });
                 }
             }
         }
 
-        WallpaperColors getWallpaperColors(int which) {
+        WallpaperColors getWallpaperColors(int which, int userId) {
             if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
                 throw new IllegalArgumentException(
                         "Must request colors for exactly one kind of wallpaper");
             }
 
             try {
-                return mService.getWallpaperColors(which);
+                return mService.getWallpaperColors(which, userId);
             } catch (RemoteException e) {
                 // Can't get colors, connection lost.
             }
@@ -857,7 +860,7 @@
      * @param listener A listener to register
      */
     public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
-        sGlobals.addOnColorsChangedListener(listener, null);
+        addOnColorsChangedListener(listener, null);
     }
 
     /**
@@ -868,25 +871,61 @@
      */
     public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
             @NonNull Handler handler) {
-        sGlobals.addOnColorsChangedListener(listener, handler);
+        addOnColorsChangedListener(listener, handler, mContext.getUserId());
+    }
+
+    /**
+     * Registers a listener to get notified when the wallpaper colors change
+     * @param listener A listener to register
+     * @param handler Where to call it from. Will be called from the main thread
+     *                if null.
+     * @param userId Owner of the wallpaper or UserHandle.USER_ALL.
+     * @hide
+     */
+    public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
+            @NonNull Handler handler, int userId) {
+        sGlobals.addOnColorsChangedListener(listener, handler, userId);
     }
 
     /**
      * Stop listening to color updates.
-     * @param callback A callback to unsubscribe
+     * @param callback A callback to unsubscribe.
      */
     public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
-        sGlobals.removeOnColorsChangedListener(callback);
+        removeOnColorsChangedListener(callback, mContext.getUserId());
+    }
+
+    /**
+     * Stop listening to color updates.
+     * @param callback A callback to unsubscribe.
+     * @param userId Owner of the wallpaper or UserHandle.USER_ALL.
+     * @hide
+     */
+    public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
+            int userId) {
+        sGlobals.removeOnColorsChangedListener(callback, userId);
     }
 
     /**
      * Get the primary colors of a wallpaper
      * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
      *     {@link #FLAG_LOCK}
-     * @return a list of colors ordered by priority
+     * @return {@link WallpaperColors} or null if colors are unknown.
      */
     public @Nullable WallpaperColors getWallpaperColors(int which) {
-        return sGlobals.getWallpaperColors(which);
+        return getWallpaperColors(which, mContext.getUserId());
+    }
+
+    /**
+     * Get the primary colors of a wallpaper
+     * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+     *     {@link #FLAG_LOCK}
+     * @param userId Owner of the wallpaper.
+     * @return {@link WallpaperColors} or null if colors are unknown.
+     * @hide
+     */
+    public @Nullable WallpaperColors getWallpaperColors(int which, int userId) {
+        return sGlobals.getWallpaperColors(which, userId);
     }
 
     /**
@@ -1902,9 +1941,9 @@
         }
 
         @Override
-        public void onWallpaperColorsChanged(WallpaperColors colors, int which)
+        public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId)
             throws RemoteException {
-            sGlobals.onWallpaperColorsChanged(colors, which);
+            sGlobals.onWallpaperColorsChanged(colors, which, userId);
         }
     }
 
@@ -1921,5 +1960,19 @@
          * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
          */
         void onColorsChanged(WallpaperColors colors, int which);
+
+        /**
+         * Called when colors change.
+         * A {@link android.app.WallpaperColors} object containing a simplified
+         * color histogram will be given.
+         *
+         * @param colors Wallpaper color info
+         * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
+         * @param userId Owner of the wallpaper
+         * @hide
+         */
+        default void onColorsChanged(WallpaperColors colors, int which, int userId) {
+            onColorsChanged(colors, which);
+        }
     }
 }
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index f987468..36f5f96 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -18,20 +18,19 @@
 
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.BitmapFactory;
-import android.graphics.Point;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.util.Slog;
-import android.view.Display;
-import android.view.WindowManager;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 
 /**
- * Helper for backing up / restoring wallpapers.  Basically an AbsoluteFileBackupHelper,
- * but with logic for deciding what to do with restored wallpaper images.
+ * We no longer back up wallpapers with this helper, but we do need to process restores
+ * of legacy backup payloads.  We just take the restored image as-is and apply it as the
+ * system wallpaper using the public "set the wallpaper" API.
  *
  * @hide
  */
@@ -39,83 +38,34 @@
     private static final String TAG = "WallpaperBackupHelper";
     private static final boolean DEBUG = false;
 
-    // If 'true', then apply an acceptable-size heuristic at restore time, dropping back
-    // to the factory default wallpaper if the restored one differs "too much" from the
-    // device's preferred wallpaper image dimensions.
-    private static final boolean REJECT_OUTSIZED_RESTORE = false;
-
-    // When outsized restore rejection is enabled, this is the maximum ratio between the
-    // source and target image heights that will be permitted.  The ratio is checked both
-    // ways (i.e. >= MAX, or <= 1/MAX) to validate restores from both largeer-than-target
-    // and smaller-than-target sources.
-    private static final double MAX_HEIGHT_RATIO = 1.35;
-
-    // The height ratio check when applying larger images on smaller screens is separate;
-    // in current policy we accept any such restore regardless of the relative dimensions.
-    private static final double MIN_HEIGHT_RATIO = 0;
-
-    // This path must match what the WallpaperManagerService uses
-    // TODO: Will need to change if backing up non-primary user's wallpaper
-    // http://b/22388012
-    public static final String WALLPAPER_IMAGE =
-            new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
-                    "wallpaper").getAbsolutePath();
-    public static final String WALLPAPER_ORIG_IMAGE =
-            new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
-                    "wallpaper_orig").getAbsolutePath();
-    public static final String WALLPAPER_INFO =
-            new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
-                    "wallpaper_info.xml").getAbsolutePath();
-    // Use old keys to keep legacy data compatibility and avoid writing two wallpapers
+    // Key that legacy wallpaper imagery was stored under
     public static final String WALLPAPER_IMAGE_KEY =
             "/data/data/com.android.settings/files/wallpaper";
     public static final String WALLPAPER_INFO_KEY = "/data/system/wallpaper_info.xml";
 
-    // Stage file - should be adjacent to the WALLPAPER_IMAGE location.  The wallpapers
-    // will be saved to this file from the restore stream, then renamed to the proper
-    // location if it's deemed suitable.
-    // TODO: Will need to change if backing up non-primary user's wallpaper
-    // http://b/22388012
+    // Stage file that the restored imagery is stored to prior to being applied
+    // as the system wallpaper.
     private static final String STAGE_FILE =
             new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
                     "wallpaper-tmp").getAbsolutePath();
 
-    Context mContext;
-    String[] mFiles;
-    String[] mKeys;
-    double mDesiredMinWidth;
-    double mDesiredMinHeight;
+    private final String[] mKeys;
+    private final WallpaperManager mWpm;
 
     /**
-     * Construct a helper for backing up / restoring the files at the given absolute locations
-     * within the file system.
+     * Legacy wallpaper restores, from back when the imagery was stored under the
+     * "android" system package as file key/value entities.
      *
      * @param context
      * @param files
      */
-    public WallpaperBackupHelper(Context context, String[] files, String[] keys) {
+    public WallpaperBackupHelper(Context context, String[] keys) {
         super(context);
 
         mContext = context;
-        mFiles = files;
         mKeys = keys;
 
-        final WindowManager wm =
-                (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        final WallpaperManager wpm =
-                (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
-        final Display d = wm.getDefaultDisplay();
-        final Point size = new Point();
-        d.getSize(size);
-        mDesiredMinWidth = Math.min(size.x, size.y);
-        mDesiredMinHeight = (double) wpm.getDesiredMinimumHeight();
-        if (mDesiredMinHeight <= 0) {
-            mDesiredMinHeight = size.y;
-        }
-
-        if (DEBUG) {
-            Slog.d(TAG, "dmW=" + mDesiredMinWidth + " dmH=" + mDesiredMinHeight);
-        }
+        mWpm = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
     }
 
     /**
@@ -126,13 +76,12 @@
     @Override
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) {
-        performBackup_checked(oldState, data, newState, mFiles, mKeys);
+        // Intentionally no-op; we don't back up the wallpaper this way any more.
     }
 
     /**
      * Restore one absolute file entity from the restore stream.  If we're restoring the
-     * magic wallpaper file, take specific action to determine whether it is suitable for
-     * the current device.
+     * magic wallpaper file, apply it as the system wallpaper.
      */
     @Override
     public void restoreEntity(BackupDataInputStream data) {
@@ -140,69 +89,21 @@
         if (isKeyInList(key, mKeys)) {
             if (key.equals(WALLPAPER_IMAGE_KEY)) {
                 // restore the file to the stage for inspection
-                File f = new File(STAGE_FILE);
-                if (writeFile(f, data)) {
-
-                    // Preflight the restored image's dimensions without loading it
-                    BitmapFactory.Options options = new BitmapFactory.Options();
-                    options.inJustDecodeBounds = true;
-                    BitmapFactory.decodeFile(STAGE_FILE, options);
-
-                    if (DEBUG) Slog.d(TAG, "Restoring wallpaper image w=" + options.outWidth
-                            + " h=" + options.outHeight);
-
-                    if (REJECT_OUTSIZED_RESTORE) {
-                        // We accept any wallpaper that is at least as wide as our preference
-                        // (i.e. wide enough to fill the screen), and is within a comfortable
-                        // factor of the target height, to avoid significant clipping/scaling/
-                        // letterboxing.  At this point we know that mDesiredMinWidth is the
-                        // smallest dimension, regardless of current orientation, so we can
-                        // safely require that the candidate's width and height both exceed
-                        // that hard minimum.
-                        final double heightRatio = mDesiredMinHeight / options.outHeight;
-                        if (options.outWidth < mDesiredMinWidth
-                                || options.outHeight < mDesiredMinWidth
-                                || heightRatio >= MAX_HEIGHT_RATIO
-                                || heightRatio <= MIN_HEIGHT_RATIO) {
-                            // Not wide enough for the screen, or too short/tall to be a good fit
-                            // for the height of the screen, broken image file, or the system's
-                            // desires for wallpaper size are in a bad state.  Probably one of the
-                            // first two.
-                            Slog.i(TAG, "Restored image dimensions (w="
-                                    + options.outWidth + ", h=" + options.outHeight
-                                    + ") too far off target (tw="
-                                    + mDesiredMinWidth + ", th=" + mDesiredMinHeight
-                                    + "); falling back to default wallpaper.");
-                            f.delete();
-                            return;
+                File stage = new File(STAGE_FILE);
+                try {
+                    if (writeFile(stage, data)) {
+                        try (FileInputStream in = new FileInputStream(stage)) {
+                            mWpm.setStream(in);
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Unable to set restored wallpaper: " + e.getMessage());
                         }
+                    } else {
+                        Slog.e(TAG, "Unable to save restored wallpaper");
                     }
-
-                    // We passed the acceptable-dimensions test (if any), so we're going to
-                    // use the restored image.  That comes last, when we are done restoring
-                    // both the pixels and the metadata.
+                } finally {
+                    stage.delete();
                 }
-            } else if (key.equals(WALLPAPER_INFO_KEY)) {
-                // XML file containing wallpaper info
-                File f = new File(WALLPAPER_INFO);
-                writeFile(f, data);
             }
         }
     }
-
-    /**
-     * Hook for the agent to call this helper upon completion of the restore.  We do this
-     * upon completion so that we know both the imagery and the wallpaper info have
-     * been emplaced without requiring either or relying on ordering.
-     */
-    public void onRestoreFinished() {
-        final File f = new File(STAGE_FILE);
-        if (f.exists()) {
-            // TODO: spin a service to copy the restored image to sd/usb storage,
-            // since it does not exist anywhere other than the private wallpaper
-            // file.
-            Slog.d(TAG, "Applying restored wallpaper image.");
-            f.renameTo(new File(WALLPAPER_ORIG_IMAGE));
-        }
-    }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index ea675fb..c3ebf55 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1406,8 +1406,9 @@
                 // Icon may have been omitted for calls that return bulk session
                 // lists, so try fetching the specific icon.
                 try {
-                    appIcon = AppGlobals.getPackageManager().getPackageInstaller()
-                            .getSessionInfo(sessionId).appIcon;
+                    final SessionInfo info = AppGlobals.getPackageManager().getPackageInstaller()
+                            .getSessionInfo(sessionId);
+                    appIcon = (info != null) ? info.appIcon : null;
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 63eedf5..55343a2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -73,8 +73,10 @@
      * Create a request suitable for still image capture. Specifically, this
      * means prioritizing image quality over frame rate. These requests would
      * commonly be used with the {@link CameraCaptureSession#capture} method.
-     * This template is guaranteed to be supported on all camera devices.
-     *
+     * This template is guaranteed to be supported on all camera devices except
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+     * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+     * BACKWARD_COMPATIBLE}.
      * @see #createCaptureRequest
      */
     public static final int TEMPLATE_STILL_CAPTURE = 2;
@@ -84,7 +86,10 @@
      * that a stable frame rate is used, and post-processing is set for
      * recording quality. These requests would commonly be used with the
      * {@link CameraCaptureSession#setRepeatingRequest} method.
-     * This template is guaranteed to be supported on all camera devices.
+     * This template is guaranteed to be supported on all camera devices except
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+     * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+     * BACKWARD_COMPATIBLE}.
      *
      * @see #createCaptureRequest
      */
@@ -98,7 +103,10 @@
      * {@link #TEMPLATE_RECORD} is is in use with {@link CameraCaptureSession#setRepeatingRequest}.
      * This template is guaranteed to be supported on all camera devices except
      * legacy devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
-     * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY})
+     * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}) and
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+     * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+     * BACKWARD_COMPATIBLE}.
      *
      * @see #createCaptureRequest
      */
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index c3bbaec..a09a686 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -23,7 +23,7 @@
 oneway interface ITunerCallback {
     void onError(int status);
     void onConfigurationChanged(in RadioManager.BandConfig config);
-    void onProgramInfoChanged();
+    void onCurrentProgramInfoChanged();
     void onTrafficAnnouncement(boolean active);
     void onEmergencyAnnouncement(boolean active);
     void onAntennaState(boolean connected);
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index 00a36c8..02ca52e 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -49,7 +49,7 @@
         synchronized (mLock) {
             if (mTuner != null) throw new IllegalStateException();
             mTuner = tuner;
-            if (mPendingProgramInfoChanged) onProgramInfoChanged();
+            if (mPendingProgramInfoChanged) onCurrentProgramInfoChanged();
         }
     }
 
@@ -64,7 +64,7 @@
     }
 
     @Override
-    public void onProgramInfoChanged() {
+    public void onCurrentProgramInfoChanged() {
         synchronized (mLock) {
             if (mTuner == null) {
                 mPendingProgramInfoChanged = true;
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index ed223d1..d2e3510 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -16,10 +16,6 @@
 
 package android.inputmethodservice;
 
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputMethodSession;
-
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -34,9 +30,13 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.InputMethodSession;
-import android.view.inputmethod.CursorAnchorInfo;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputMethodSession;
 
 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
         implements HandlerCaller.Callback {
@@ -218,7 +218,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             if (mInputMethodSession == null) {
                 // The session has been finished.
                 finishInputEvent(event, false);
diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java
index fdb4ba0..a70c97b 100644
--- a/core/java/android/net/NetworkRecommendationProvider.java
+++ b/core/java/android/net/NetworkRecommendationProvider.java
@@ -35,6 +35,7 @@
  * A network recommendation provider is any application which:
  * <ul>
  * <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
  * <li>Includes a Service for the {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} intent
  *     which is protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE} permission.
  * </ul>
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 7e0c9ce..060af0d 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -38,7 +38,8 @@
  *
  * <p>A network scorer is any application which:
  * <ul>
- * <li>Declares the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
  * <li>Include a Service for the {@link #ACTION_RECOMMEND_NETWORKS} action
  *     protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE}
  *     permission.
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index ace3748..1e41eea 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -16,6 +16,10 @@
 
 package android.net.nsd;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
+
 import android.annotation.SdkConstant;
 import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -240,12 +244,12 @@
         return name;
     }
 
+    private static int FIRST_LISTENER_KEY = 1;
+
     private final INsdManager mService;
     private final Context mContext;
 
-    private static final int INVALID_LISTENER_KEY = 0;
-    private static final int BUSY_LISTENER_KEY = -1;
-    private int mListenerKey = 1;
+    private int mListenerKey = FIRST_LISTENER_KEY;
     private final SparseArray mListenerMap = new SparseArray();
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
     private final Object mMapLock = new Object();
@@ -311,7 +315,6 @@
         public void onServiceFound(NsdServiceInfo serviceInfo);
 
         public void onServiceLost(NsdServiceInfo serviceInfo);
-
     }
 
     /** Interface for callback invocation for service registration */
@@ -342,8 +345,9 @@
 
         @Override
         public void handleMessage(Message message) {
-            if (DBG) Log.d(TAG, "received " + nameOf(message.what));
-            switch (message.what) {
+            final int what = message.what;
+            final int key = message.arg2;
+            switch (what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                     mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                     return;
@@ -356,19 +360,26 @@
                 default:
                     break;
             }
-            Object listener = getListener(message.arg2);
+            final Object listener;
+            final NsdServiceInfo ns;
+            synchronized (mMapLock) {
+                listener = mListenerMap.get(key);
+                ns = mServiceMap.get(key);
+            }
             if (listener == null) {
                 Log.d(TAG, "Stale key " + message.arg2);
                 return;
             }
-            NsdServiceInfo ns = getNsdService(message.arg2);
-            switch (message.what) {
+            if (DBG) {
+                Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+            }
+            switch (what) {
                 case DISCOVER_SERVICES_STARTED:
                     String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
                     ((DiscoveryListener) listener).onDiscoveryStarted(s);
                     break;
                 case DISCOVER_SERVICES_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
                             message.arg1);
                     break;
@@ -381,16 +392,16 @@
                 case STOP_DISCOVERY_FAILED:
                     // TODO: failure to stop discovery should be internal and retried internally, as
                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
                             message.arg1);
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
                     break;
                 case REGISTER_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
@@ -398,7 +409,7 @@
                             (NsdServiceInfo) message.obj);
                     break;
                 case UNREGISTER_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
@@ -408,11 +419,11 @@
                     ((RegistrationListener) listener).onServiceUnregistered(ns);
                     break;
                 case RESOLVE_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
                     break;
                 default:
@@ -422,40 +433,27 @@
         }
     }
 
-    // if the listener is already in the map, reject it.  Otherwise, add it and
-    // return its key.
+    private int nextListenerKey() {
+        // Ensure mListenerKey >= FIRST_LISTENER_KEY;
+        mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
+        return mListenerKey;
+    }
+
+    // Assert that the listener is not in the map, then add it and returns its key
     private int putListener(Object listener, NsdServiceInfo s) {
-        if (listener == null) return INVALID_LISTENER_KEY;
-        int key;
+        checkListener(listener);
+        final int key;
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            if (valueIndex != -1) {
-                return BUSY_LISTENER_KEY;
-            }
-            do {
-                key = mListenerKey++;
-            } while (key == INVALID_LISTENER_KEY);
+            checkArgument(valueIndex == -1, "listener already in use");
+            key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
         }
         return key;
     }
 
-    private Object getListener(int key) {
-        if (key == INVALID_LISTENER_KEY) return null;
-        synchronized (mMapLock) {
-            return mListenerMap.get(key);
-        }
-    }
-
-    private NsdServiceInfo getNsdService(int key) {
-        synchronized (mMapLock) {
-            return mServiceMap.get(key);
-        }
-    }
-
     private void removeListener(int key) {
-        if (key == INVALID_LISTENER_KEY) return;
         synchronized (mMapLock) {
             mListenerMap.remove(key);
             mServiceMap.remove(key);
@@ -463,16 +461,15 @@
     }
 
     private int getListenerKey(Object listener) {
+        checkListener(listener);
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            if (valueIndex != -1) {
-                return mListenerMap.keyAt(valueIndex);
-            }
+            checkArgument(valueIndex != -1, "listener not registered");
+            return mListenerMap.keyAt(valueIndex);
         }
-        return INVALID_LISTENER_KEY;
     }
 
-    private String getNsdServiceInfoType(NsdServiceInfo s) {
+    private static String getNsdServiceInfoType(NsdServiceInfo s) {
         if (s == null) return "?";
         return s.getServiceType();
     }
@@ -482,7 +479,9 @@
      */
     private void init() {
         final Messenger messenger = getMessenger();
-        if (messenger == null) throw new RuntimeException("Failed to initialize");
+        if (messenger == null) {
+            fatal("Failed to obtain service Messenger");
+        }
         HandlerThread t = new HandlerThread("NsdManager");
         t.start();
         mHandler = new ServiceHandler(t.getLooper());
@@ -490,10 +489,15 @@
         try {
             mConnected.await();
         } catch (InterruptedException e) {
-            Log.e(TAG, "interrupted wait at init");
+            fatal("Interrupted wait at init");
         }
     }
 
+    private static void fatal(String msg) {
+        Log.e(TAG, msg);
+        throw new RuntimeException(msg);
+    }
+
     /**
      * Register a service to be discovered by other services.
      *
@@ -513,23 +517,10 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
-        if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
-                TextUtils.isEmpty(serviceInfo.getServiceType())) {
-            throw new IllegalArgumentException("Service name or type cannot be empty");
-        }
-        if (serviceInfo.getPort() <= 0) {
-            throw new IllegalArgumentException("Invalid port number");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-        if (protocolType != PROTOCOL_DNS_SD) {
-            throw new IllegalArgumentException("Unsupported protocol");
-        }
+        checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+        checkServiceInfo(serviceInfo);
+        checkProtocol(protocolType);
         int key = putListener(listener, serviceInfo);
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
         mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
     }
 
@@ -548,12 +539,6 @@
      */
     public void unregisterService(RegistrationListener listener) {
         int id = getListenerKey(listener);
-        if (id == INVALID_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener not registered");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
         mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
     }
 
@@ -586,25 +571,13 @@
      * Cannot be null. Cannot be in use for an active service discovery.
      */
     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-        if (TextUtils.isEmpty(serviceType)) {
-            throw new IllegalArgumentException("Service type cannot be empty");
-        }
-
-        if (protocolType != PROTOCOL_DNS_SD) {
-            throw new IllegalArgumentException("Unsupported protocol");
-        }
+        checkStringNotEmpty(serviceType, "Service type cannot be empty");
+        checkProtocol(protocolType);
 
         NsdServiceInfo s = new NsdServiceInfo();
         s.setServiceType(serviceType);
 
         int key = putListener(listener, s);
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
-
         mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
     }
 
@@ -626,12 +599,6 @@
      */
     public void stopServiceDiscovery(DiscoveryListener listener) {
         int id = getListenerKey(listener);
-        if (id == INVALID_LISTENER_KEY) {
-            throw new IllegalArgumentException("service discovery not active on listener");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
         mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
     }
 
@@ -645,19 +612,8 @@
      * Cannot be in use for an active service resolution.
      */
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
-        if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
-                TextUtils.isEmpty(serviceInfo.getServiceType())) {
-            throw new IllegalArgumentException("Service name or type cannot be empty");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-
+        checkServiceInfo(serviceInfo);
         int key = putListener(listener, serviceInfo);
-
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
         mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
     }
 
@@ -671,10 +627,10 @@
     }
 
     /**
-     * Get a reference to NetworkService handler. This is used to establish
+     * Get a reference to NsdService handler. This is used to establish
      * an AsyncChannel communication with the service
      *
-     * @return Messenger pointing to the NetworkService handler
+     * @return Messenger pointing to the NsdService handler
      */
     private Messenger getMessenger() {
         try {
@@ -683,4 +639,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    private static void checkListener(Object listener) {
+        checkNotNull(listener, "listener cannot be null");
+    }
+
+    private static void checkProtocol(int protocolType) {
+        checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+    }
+
+    private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+        checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
+        checkStringNotEmpty(serviceInfo.getServiceName(),"Service name cannot be empty");
+        checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 7d664ec..efc911a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3544,6 +3544,7 @@
                     if (name.indexOf(',') >= 0) {
                         name = name.replace(',', '_');
                     }
+                    name = name.replaceAll("[\\n|\\r]+", "");
                     dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString());
                 }
             }
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 712bbaa..459aeb0 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -28,6 +28,7 @@
 import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
+import java.nio.NioUtils;
 
 import sun.misc.Cleaner;
 
@@ -191,11 +192,16 @@
     }
 
     /**
-     * Creates an mmap of the SharedMemory with the specified prot, offset, and length.
+     * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
+     * always produce a new ByteBuffer window to the backing shared memory region. Every call
+     * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
+     * returned by map() is no longer needed.
      *
      * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
-     * @param offset The offset into the shared memory to begin mapping
-     * @param length The length of the region to map
+     * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
+     *         getSize().
+     * @param length The length of the region to map. Must be > 0 and offset + length must not
+     *         exceed getSize().
      * @return A ByteBuffer mapping.
      * @throws ErrnoException if the mmap call failed.
      */
@@ -203,7 +209,7 @@
         checkOpen();
         validateProt(prot);
         if (offset < 0) {
-            throw new IllegalArgumentException("Offset must be > 0");
+            throw new IllegalArgumentException("Offset must be >= 0");
         }
         if (length <= 0) {
             throw new IllegalArgumentException("Length must be > 0");
@@ -218,15 +224,16 @@
     }
 
     /**
-     * Unmaps a buffer previously returned by {@link #map(int, int, int)}
+     * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
+     * release the backing memory of the ByteBuffer, invalidating all references to it. Only
+     * call this method if there are no duplicates of the ByteBuffer in use and don't
+     * access the ByteBuffer after calling this method.
+     *
      * @param buffer The buffer to unmap
      */
     public static void unmap(@NonNull ByteBuffer buffer) {
         if (buffer instanceof DirectByteBuffer) {
-            Cleaner cleaner = ((DirectByteBuffer) buffer).cleaner();
-            if (cleaner != null) {
-                cleaner.clean();
-            }
+            NioUtils.freeDirectBuffer(buffer);
         } else {
             throw new IllegalArgumentException(
                     "ByteBuffer wasn't created by #map(int, int, int); can't unmap");
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 2298134..81b1921 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -66,8 +66,6 @@
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
 import java.util.LinkedList;
 import java.util.Objects;
 
@@ -635,30 +633,6 @@
     }
 
     /**
-     * Returns metadata for arbitrary file given its stream and mimetype.
-     *
-     * <p><b>Note: Providers should only call this with streams for locally cached resources.
-     * Use of network backed streams is inadvisable for performance reasons.
-     *
-     * @param stream The input stream. Should be backed by locally cached content.
-     *       Client retains ownership of the stream.
-     * @param mimeType The mime type of the file.
-     * @param tags The list of tags to load, if known. Pass null to get a default set.
-     * @return Bundle containing any metadata found.
-     * @throws IOException in the event of an error reading metadata.
-     *
-     * @hide
-     */
-    protected Bundle getDocumentMetadataFromStream(InputStream stream, String mimeType)
-            throws IOException {
-        Bundle metadata = new Bundle();
-        // TODO: Remove the last null arg from MetadataReader. It was the "tags" value,
-        // the has been removed from the getDocumentMetadata method.
-        MetadataReader.getMetadata(metadata, stream, mimeType, null);
-        return metadata;
-    }
-
-    /**
      * Return concrete MIME type of the requested document. Must match the value
      * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
      * implementation queries {@link #queryDocument(String, String[])}, so
diff --git a/core/java/android/provider/MetadataReader.java b/core/java/android/provider/MetadataReader.java
index 5aa5f72..4f3a7d6 100644
--- a/core/java/android/provider/MetadataReader.java
+++ b/core/java/android/provider/MetadataReader.java
@@ -36,29 +36,33 @@
  */
 public final class MetadataReader {
 
-    private MetadataReader() {
-    }
+    private MetadataReader() {}
 
     private static final String[] DEFAULT_EXIF_TAGS = {
-            ExifInterface.TAG_IMAGE_WIDTH,
-            ExifInterface.TAG_IMAGE_LENGTH,
+            ExifInterface.TAG_APERTURE,
+            ExifInterface.TAG_COPYRIGHT,
             ExifInterface.TAG_DATETIME,
+            ExifInterface.TAG_EXPOSURE_TIME,
+            ExifInterface.TAG_FOCAL_LENGTH,
+            ExifInterface.TAG_F_NUMBER,
             ExifInterface.TAG_GPS_LATITUDE,
             ExifInterface.TAG_GPS_LATITUDE_REF,
             ExifInterface.TAG_GPS_LONGITUDE,
             ExifInterface.TAG_GPS_LONGITUDE_REF,
+            ExifInterface.TAG_IMAGE_LENGTH,
+            ExifInterface.TAG_IMAGE_WIDTH,
+            ExifInterface.TAG_ISO_SPEED_RATINGS,
             ExifInterface.TAG_MAKE,
             ExifInterface.TAG_MODEL,
-            ExifInterface.TAG_APERTURE,
-            ExifInterface.TAG_SHUTTER_SPEED_VALUE
+            ExifInterface.TAG_ORIENTATION,
+            ExifInterface.TAG_SHUTTER_SPEED_VALUE,
     };
 
-    private static final Map<String, Integer> TYPE_MAPPING = new HashMap<>();
-    private static final String[] ALL_KNOWN_EXIF_KEYS;
     private static final int TYPE_INT = 0;
     private static final int TYPE_DOUBLE = 1;
     private static final int TYPE_STRING = 2;
 
+    private static final Map<String, Integer> TYPE_MAPPING = new HashMap<>();
     static {
         // TODO: Move this over to ExifInterface.java
         // Since each ExifInterface item has a type, and there's currently no way to get the type
@@ -198,11 +202,19 @@
         TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, TYPE_INT);
         TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, TYPE_INT);
         TYPE_MAPPING.put(ExifInterface.TAG_RW2_ISO, TYPE_INT);
-        ALL_KNOWN_EXIF_KEYS = TYPE_MAPPING.keySet().toArray(new String[TYPE_MAPPING.size()]);
     }
     private static final String JPG_MIME_TYPE = "image/jpg";
     private static final String JPEG_MIME_TYPE = "image/jpeg";
 
+
+    /**
+     * Returns true if caller can generally expect to get metadata results
+     * for the supplied mimetype.
+     */
+    public static boolean isSupportedMimeType(String mimeType) {
+        return JPG_MIME_TYPE.equals(mimeType) || JPEG_MIME_TYPE.equals(mimeType);
+    }
+
     /**
      * Generic metadata retrieval method that can retrieve any available metadata from a given doc
      * Currently only functions for exifdata
@@ -211,27 +223,14 @@
      * @param stream InputStream containing a file
      * @param mimeType type of the given file
      * @param tags a variable amount of keys to differentiate which tags the user wants
-     *             if null, returns a default set of data from the following keys:
-     *             Exif data:
-     *             ExifInterface.TAG_IMAGE_WIDTH,
-     *             ExifInterface.TAG_IMAGE_LENGTH,
-     *             ExifInterface.TAG_DATETIME,
-     *             ExifInterface.TAG_GPS_LATITUDE,
-     *             ExifInterface.TAG_GPS_LATITUDE_REF,
-     *             ExifInterface.TAG_GPS_LONGITUDE,
-     *             ExifInterface.TAG_GPS_LONGITUDE_REF,
-     *             ExifInterface.TAG_MAKE,
-     *             ExifInterface.TAG_MODEL,
-     *             ExifInterface.TAG_APERTURE,
-     *             ExifInterface.TAG_SHUTTER_SPEED_VALUE
+     *             if null, returns a default set of data. See {@link DEFAULT_EXIF_TAGS}.
      * @throws IOException when the file doesn't exist
      */
     public static void getMetadata(Bundle metadata, InputStream stream, String mimeType,
             @Nullable String[] tags) throws IOException {
-        List<String> metadataTypes = new ArrayList();
-        if (mimeType.equals(JPG_MIME_TYPE) || mimeType.equals(JPEG_MIME_TYPE)) {
-            ExifInterface exifInterface = new ExifInterface(stream);
-            Bundle exifData = getExifData(exifInterface, tags);
+        List<String> metadataTypes = new ArrayList<>();
+        if (isSupportedMimeType(mimeType)) {
+            Bundle exifData = getExifData(stream, tags);
             if (exifData.size() > 0) {
                 metadata.putBundle(DocumentsContract.METADATA_EXIF, exifData);
                 metadataTypes.add(DocumentsContract.METADATA_EXIF);
@@ -246,27 +245,17 @@
     /**
      * Helper method that is called if getMetadata is called for an image mimeType.
      *
-     * @param exif the bundle to which we add exif data.
-     * @param exifInterface an ExifInterface for an image
+     * @param stream the input stream from which to extra data.
      * @param tags a list of ExifInterface tags that are used to retrieve data.
-     *             if null, returns a default set of data from the following keys:
-     *             ExifInterface.TAG_IMAGE_WIDTH,
-     *             ExifInterface.TAG_IMAGE_LENGTH,
-     *             ExifInterface.TAG_DATETIME,
-     *             ExifInterface.TAG_GPS_LATITUDE,
-     *             ExifInterface.TAG_GPS_LATITUDE_REF,
-     *             ExifInterface.TAG_GPS_LONGITUDE,
-     *             ExifInterface.TAG_GPS_LONGITUDE_REF,
-     *             ExifInterface.TAG_MAKE,
-     *             ExifInterface.TAG_MODEL,
-     *             ExifInterface.TAG_APERTURE,
-     *             ExifInterface.TAG_SHUTTER_SPEED_VALUE
+     *             if null, returns a default set of data. See {@link DEFAULT_EXIF_TAGS}.
      */
-    private static Bundle getExifData(ExifInterface exifInterface, @Nullable String[] tags)
+    private static Bundle getExifData(InputStream stream, @Nullable String[] tags)
             throws IOException {
         if (tags == null) {
             tags = DEFAULT_EXIF_TAGS;
         }
+
+        ExifInterface exifInterface = new ExifInterface(stream);
         Bundle exif = new Bundle();
         for (String tag : tags) {
             if (TYPE_MAPPING.get(tag).equals(TYPE_INT)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8869a6b..88aafdc 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -576,21 +576,6 @@
             "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
 
     /**
-     * Activity Action: Show a dialog to select input method.
-     * <p>
-     * In some cases, a matching Activity may not exist, so ensure you
-     * safeguard against this.
-     * <p>
-     * Input: Nothing.
-     * <p>
-     * Output: Nothing.
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SHOW_INPUT_METHOD_PICKER =
-            "android.settings.SHOW_INPUT_METHOD_PICKER";
-
-    /**
      * Activity Action: Show settings to manage the user input dictionary.
      * <p>
      * Starting with {@link android.os.Build.VERSION_CODES#KITKAT},
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index eff52e6..fb6f637 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -30,5 +30,6 @@
     void dispatchPointer(in MotionEvent event);
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras);
+    void requestWallpaperColors();
     void destroy();
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 424967f..65d66dc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -105,6 +105,7 @@
     private static final int MSG_WINDOW_RESIZED = 10030;
     private static final int MSG_WINDOW_MOVED = 10035;
     private static final int MSG_TOUCH_EVENT = 10040;
+    private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
     
     private final ArrayList<Engine> mActiveEngines
             = new ArrayList<Engine>();
@@ -268,7 +269,7 @@
             }
 
             @Override
-            public void onInputEvent(InputEvent event) {
+            public void onInputEvent(InputEvent event, int displayId) {
                 boolean handled = false;
                 try {
                     if (event instanceof MotionEvent
@@ -626,8 +627,7 @@
                 }
                 Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
                 mCaller.sendMessage(msg);
-            } else {
-                event.recycle();
+            } else {event.recycle();
             }
         }
 
@@ -1192,6 +1192,11 @@
             }
         }
 
+        public void requestWallpaperColors() {
+            Message msg = mCaller.obtainMessage(MSG_REQUEST_WALLPAPER_COLORS);
+            mCaller.sendMessage(msg);
+        }
+
         public void destroy() {
             Message msg = mCaller.obtainMessage(DO_DETACH);
             mCaller.sendMessage(msg);
@@ -1210,7 +1215,6 @@
                     mEngine = engine;
                     mActiveEngines.add(engine);
                     engine.attach(this);
-                    engine.notifyColorsChanged();
                     return;
                 }
                 case DO_DETACH: {
@@ -1268,6 +1272,16 @@
                     }
                     ev.recycle();
                 } break;
+                case MSG_REQUEST_WALLPAPER_COLORS: {
+                    if (mConnection == null) {
+                        break;
+                    }
+                    try {
+                        mConnection.onWallpaperColorsChanged(mEngine.onComputeColors());
+                    } catch (RemoteException e) {
+                        // Connection went away, nothing to do in here.
+                    }
+                } break;
                 default :
                     Log.w(TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index 1f1863c..2900c83 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -106,6 +106,7 @@
             final PlaybackQueueItem item = it.next();
             if (item.getCallerIdentity() == callerIdentity) {
                 it.remove();
+                stop(item);
             }
         }
     }
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index f52638b..704a1da 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -70,6 +71,11 @@
     // wait for the next one.
     private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
 
+    private static final int NOT_RUN = 0;
+    private static final int RUN_CALLED = 1;
+    private static final int STOP_CALLED = 2;
+    private final AtomicInteger mRunState = new AtomicInteger(NOT_RUN);
+
     SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
             int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
             Object callerIdentity, AbstractEventLogger logger) {
@@ -88,6 +94,11 @@
 
     @Override
     public void run() {
+        if (!mRunState.compareAndSet(NOT_RUN, RUN_CALLED)) {
+            // stop() was already called before run(). Do nothing and just finish.
+            return;
+        }
+
         final UtteranceProgressDispatcher dispatcher = getDispatcher();
         dispatcher.dispatchOnStart();
 
@@ -120,6 +131,12 @@
 
         mAudioTrack.waitAndRelease();
 
+        dispatchEndStatus();
+    }
+
+    private void dispatchEndStatus() {
+        final UtteranceProgressDispatcher dispatcher = getDispatcher();
+
         if (mStatusCode == TextToSpeech.SUCCESS) {
             dispatcher.dispatchOnSuccess();
         } else if(mStatusCode == TextToSpeech.STOPPED) {
@@ -140,6 +157,13 @@
             mStopped = true;
             mStatusCode = statusCode;
 
+            if (mRunState.getAndSet(STOP_CALLED) == NOT_RUN) {
+                // Dispatch the status code and just finish without signaling
+                // if run() has not even started.
+                dispatchEndStatus();
+                return;
+            }
+
             // Wake up the audio playback thread if it was waiting on take().
             // take() will return null since mStopped was true, and will then
             // break out of the data write loop.
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
index d773158..a507f2b 100644
--- a/core/java/android/text/InputFilter.java
+++ b/core/java/android/text/InputFilter.java
@@ -16,7 +16,9 @@
 
 package android.text;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
 
 import java.util.Locale;
 
@@ -64,7 +66,8 @@
          * Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that
          * locale are used for transforming the sequence.
          */
-        public AllCaps(@Nullable Locale locale) {
+        public AllCaps(@NonNull Locale locale) {
+            Preconditions.checkNotNull(locale);
             mLocale = locale;
         }
 
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4140464..3474c2b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -559,7 +559,7 @@
         Builder.recycle(b);
     }
 
-    /* package */ StaticLayout(@Nullable CharSequence text) {
+    /* package */ StaticLayout(CharSequence text) {
         super(text, null, 0, null, 0, 0);
 
         mColumns = COLUMNS_ELLIPSIZE;
@@ -601,7 +601,7 @@
     }
 
     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
-        final CharSequence source = b.mText;
+        CharSequence source = b.mText;
         int bufStart = b.mStart;
         int bufEnd = b.mEnd;
         TextPaint paint = b.mPaint;
@@ -720,7 +720,7 @@
                     // TODO: Support more justification mode, e.g. letter spacing, stretching.
                     b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
             if (mLeftIndents != null || mRightIndents != null) {
-                // TODO(performance): it would be better to do this once per layout rather
+                // TODO(raph) performance: it would be better to do this once per layout rather
                 // than once per paragraph, but that would require a change to the native
                 // interface.
                 int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -807,7 +807,7 @@
                             width += widths[j];
                         }
                     }
-                    flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit
+                    flag |= flags[i] & TAB_MASK;
                 }
                 // Treat the last line and overflowed lines as a single line.
                 breaks[remainingLineCount - 1] = breaks[breakCount - 1];
@@ -909,16 +909,17 @@
         }
     }
 
-    // The parameters that are not changed in the method are marked as final to make the code
-    // easier to understand.
-    private int out(final CharSequence text, final int start, final int end, int above, int below,
-            int top, int bottom, int v, final float spacingmult, final float spacingadd,
-            final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
-            final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
-            final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
-            final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
-            final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
-            final float textWidth, final TextPaint paint, final boolean moreChars) {
+    private int out(CharSequence text, int start, int end,
+                      int above, int below, int top, int bottom, int v,
+                      float spacingmult, float spacingadd,
+                      LineHeightSpan[] chooseHt, int[] chooseHtv,
+                      Paint.FontMetricsInt fm, int flags,
+                      boolean needMultiply, byte[] chdirs, int dir,
+                      boolean easy, int bufEnd, boolean includePad,
+                      boolean trackPad, boolean addLastLineLineSpacing, char[] chs,
+                      float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
+                      float ellipsisWidth, float textWidth,
+                      TextPaint paint, boolean moreChars) {
         final int j = mLineCount;
         final int off = j * mColumns;
         final int want = off + mColumns + TOP;
@@ -938,30 +939,30 @@
             mLineDirections = grow;
         }
 
-        lines[off + START] = start;
-        lines[off + TOP] = v;
+        if (chooseHt != null) {
+            fm.ascent = above;
+            fm.descent = below;
+            fm.top = top;
+            fm.bottom = bottom;
 
-        // Information about hyphenation, tabs, and directions are needed for determining
-        // ellipsization, so the values should be assigned before ellipsization.
+            for (int i = 0; i < chooseHt.length; i++) {
+                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
+                    ((LineHeightSpan.WithDensity) chooseHt[i]).
+                        chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
 
-        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
-        // one bit for start field
-        lines[off + TAB] |= flags & TAB_MASK;
-        lines[off + HYPHEN] = flags;
+                } else {
+                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
+                }
+            }
 
-        lines[off + DIR] |= dir << DIR_SHIFT;
-        // easy means all chars < the first RTL, so no emoji, no nothing
-        // XXX a run with no text or all spaces is easy but might be an empty
-        // RTL paragraph.  Make sure easy is false if this is the case.
-        if (easy) {
-            mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
-        } else {
-            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
-                    start - widthStart, end - start);
+            above = fm.ascent;
+            below = fm.descent;
+            top = fm.top;
+            bottom = fm.bottom;
         }
 
-        final boolean firstLine = (j == 0);
-        final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
+        boolean firstLine = (j == 0);
+        boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
 
         if (ellipsize != null) {
             // If there is only one line, then do any type of ellipsis except when it is MARQUEE
@@ -974,9 +975,9 @@
                     (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                             ellipsize == TextUtils.TruncateAt.END);
             if (doEllipsis) {
-                calculateEllipsis(text, start, end, widths, widthStart,
-                        ellipsisWidth - getTotalInsets(j), ellipsize, j,
-                        textWidth, paint, forceEllipsis, dir);
+                calculateEllipsis(start, end, widths, widthStart,
+                        ellipsisWidth, ellipsize, j,
+                        textWidth, paint, forceEllipsis);
             }
         }
 
@@ -995,28 +996,6 @@
             }
         }
 
-        if (chooseHt != null) {
-            fm.ascent = above;
-            fm.descent = below;
-            fm.top = top;
-            fm.bottom = bottom;
-
-            for (int i = 0; i < chooseHt.length; i++) {
-                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
-                    ((LineHeightSpan.WithDensity) chooseHt[i])
-                        .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
-
-                } else {
-                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
-                }
-            }
-
-            above = fm.ascent;
-            below = fm.descent;
-            top = fm.top;
-            bottom = fm.bottom;
-        }
-
         if (firstLine) {
             if (trackPad) {
                 mTopPadding = top - above;
@@ -1027,6 +1006,8 @@
             }
         }
 
+        int extra;
+
         if (lastLine) {
             if (trackPad) {
                 mBottomPadding = bottom - below;
@@ -1037,9 +1018,8 @@
             }
         }
 
-        final int extra;
         if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
-            final double ex = (below - above) * (spacingmult - 1) + spacingadd;
+            double ex = (below - above) * (spacingmult - 1) + spacingadd;
             if (ex >= 0) {
                 extra = (int)(ex + EXTRA_ROUNDING);
             } else {
@@ -1049,6 +1029,8 @@
             extra = 0;
         }
 
+        lines[off + START] = start;
+        lines[off + TOP] = v;
         lines[off + DESCENT] = below + extra;
         lines[off + EXTRA] = extra;
 
@@ -1056,7 +1038,7 @@
         // store the height as if it was ellipsized
         if (!mEllipsized && currentLineIsTheLastVisibleOne) {
             // below calculation as if it was the last line
-            final int maxLineBelow = includePad ? bottom : below;
+            int maxLineBelow = includePad ? bottom : below;
             // similar to the calculation of v below, without the extra.
             mMaxLineHeight = v + (maxLineBelow - above);
         }
@@ -1065,13 +1047,33 @@
         lines[off + mColumns + START] = end;
         lines[off + mColumns + TOP] = v;
 
+        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
+        // one bit for start field
+        lines[off + TAB] |= flags & TAB_MASK;
+        lines[off + HYPHEN] = flags;
+
+        lines[off + DIR] |= dir << DIR_SHIFT;
+        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+        // easy means all chars < the first RTL, so no emoji, no nothing
+        // XXX a run with no text or all spaces is easy but might be an empty
+        // RTL paragraph.  Make sure easy is false if this is the case.
+        if (easy) {
+            mLineDirections[j] = linedirs;
+        } else {
+            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
+                    start - widthStart, end - start);
+        }
+
         mLineCount++;
         return v;
     }
 
-    private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
-            TextPaint paint, boolean forceEllipsis, int dir) {
+    private void calculateEllipsis(int lineStart, int lineEnd,
+                                   float[] widths, int widthStart,
+                                   float avail, TextUtils.TruncateAt where,
+                                   int line, float textWidth, TextPaint paint,
+                                   boolean forceEllipsis) {
+        avail -= getTotalInsets(line);
         if (textWidth <= avail && !forceEllipsis) {
             // Everything fits!
             mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1079,47 +1081,11 @@
             return;
         }
 
-        float tempAvail = avail;
-        int numberOfTries = 0;
-        boolean lineFits = false;
-        do {
-            final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
-                    widthStart, tempAvail, where, line, textWidth, paint, forceEllipsis, dir);
-            if (ellipsizedWidth <= avail) {
-                lineFits = true;
-            } else {
-                numberOfTries++;
-                if (numberOfTries > 10) {
-                    // If the text still doesn't fit after ten tries, assume it will never fit and
-                    // ellipsize it all.
-                    mLines[mColumns * line + ELLIPSIS_START] = 0;
-                    mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
-                    lineFits = true;
-                } else {
-                    // Some side effect of ellipsization has caused the text to go over the
-                    // available width. Let's make the available width shorter by exactly that
-                    // amount and retry.
-                    tempAvail -= ellipsizedWidth - avail;
-                }
-            }
-        } while (!lineFits);
-        mEllipsized = true;
-    }
+        float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
+        int ellipsisStart = 0;
+        int ellipsisCount = 0;
+        int len = lineEnd - lineStart;
 
-    // Returns the width of the ellipsized line which in some rare cases can actually be larger
-    // than 'avail' (due to kerning or other context-based effect of replacement of text by
-    // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
-    // returns 0 so the caller can stop iterating.
-    private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
-            TextPaint paint, boolean forceEllipsis, int dir) {
-        final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
-        final int ellipsisStart;
-        final int ellipsisCount;
-        final int len = lineEnd - lineStart;
-        final int offset = lineStart - widthStart;
-
-        int hyphen = getHyphen(line);
         // We only support start ellipsis on a single line
         if (where == TextUtils.TruncateAt.START) {
             if (mMaximumVisibleLineCount == 1) {
@@ -1127,9 +1093,9 @@
                 int i;
 
                 for (i = len; i > 0; i--) {
-                    final float w = widths[i - 1 + offset];
+                    float w = widths[i - 1 + lineStart - widthStart];
                     if (w + sum + ellipsisWidth > avail) {
-                        while (i < len && widths[i + offset] == 0.0f) {
+                        while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
                             i++;
                         }
                         break;
@@ -1140,13 +1106,9 @@
 
                 ellipsisStart = 0;
                 ellipsisCount = i;
-                // Strip the potential hyphenation at beginning of line.
-                hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
             } else {
-                ellipsisStart = 0;
-                ellipsisCount = 0;
                 if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "Start ellipsis only supported with one line");
+                    Log.w(TAG, "Start Ellipsis only supported with one line");
                 }
             }
         } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
@@ -1155,7 +1117,7 @@
             int i;
 
             for (i = 0; i < len; i++) {
-                final float w = widths[i + offset];
+                float w = widths[i + lineStart - widthStart];
 
                 if (w + sum + ellipsisWidth > avail) {
                     break;
@@ -1164,27 +1126,24 @@
                 sum += w;
             }
 
-            if (forceEllipsis && i == len && len > 0) {
+            ellipsisStart = i;
+            ellipsisCount = len - i;
+            if (forceEllipsis && ellipsisCount == 0 && len > 0) {
                 ellipsisStart = len - 1;
                 ellipsisCount = 1;
-            } else {
-                ellipsisStart = i;
-                ellipsisCount = len - i;
             }
-            // Strip the potential hyphenation at end of line.
-            hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
-        } else { // where = TextUtils.TruncateAt.MIDDLE
-            // We only support middle ellipsis on a single line.
+        } else {
+            // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
             if (mMaximumVisibleLineCount == 1) {
                 float lsum = 0, rsum = 0;
                 int left = 0, right = len;
 
-                final float ravail = (avail - ellipsisWidth) / 2;
+                float ravail = (avail - ellipsisWidth) / 2;
                 for (right = len; right > 0; right--) {
-                    final float w = widths[right - 1 + offset];
+                    float w = widths[right - 1 + lineStart - widthStart];
 
                     if (w + rsum > ravail) {
-                        while (right < len && widths[right + offset] == 0.0f) {
+                        while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
                             right++;
                         }
                         break;
@@ -1192,9 +1151,9 @@
                     rsum += w;
                 }
 
-                final float lavail = avail - ellipsisWidth - rsum;
+                float lavail = avail - ellipsisWidth - rsum;
                 for (left = 0; left < right; left++) {
-                    final float w = widths[left + offset];
+                    float w = widths[left + lineStart - widthStart];
 
                     if (w + lsum > lavail) {
                         break;
@@ -1206,53 +1165,14 @@
                 ellipsisStart = left;
                 ellipsisCount = right - left;
             } else {
-                ellipsisStart = 0;
-                ellipsisCount = 0;
                 if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "Middle ellipsis only supported with one line");
+                    Log.w(TAG, "Middle Ellipsis only supported with one line");
                 }
             }
         }
+        mEllipsized = true;
         mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
-
-        if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
-            // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
-            return 0.0f;
-        }
-
-        final boolean isSpanned = text instanceof Spanned;
-        final Ellipsizer ellipsizedText = isSpanned
-                        ? new SpannedEllipsizer(text)
-                        : new Ellipsizer(text);
-        ellipsizedText.mLayout = this;
-        ellipsizedText.mMethod = where;
-
-        final boolean hasTabs = getLineContainsTab(line);
-        final TabStops tabStops;
-        if (hasTabs && isSpanned) {
-            final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
-                    lineEnd, TabStopSpan.class);
-            if (tabs.length == 0) {
-                tabStops = null;
-            } else {
-                tabStops = new TabStops(TAB_INCREMENT, tabs);
-            }
-        } else {
-            tabStops = null;
-        }
-        final TextLine textline = TextLine.obtain();
-        paint.setHyphenEdit(hyphen);
-        textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
-                hasTabs, tabStops);
-        // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
-        // converts it to an actual width. Note that we don't want to use the absolute value,
-        // since we may actually have glyphs with negative advances, which by definition always
-        // fit.
-        final float ellipsizedWidth = textline.metrics(null) * dir;
-        paint.setHyphenEdit(0);
-        TextLine.recycle(textline);
-        return ellipsizedWidth;
     }
 
     private float getTotalInsets(int line) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 463a986..979c43c 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -89,8 +89,8 @@
 
     /** {@hide} */
     @NonNull
-    public static String getEllipsisString(@NonNull TruncateAt method) {
-        return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
+    public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) {
+        return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
     }
 
 
@@ -1187,11 +1187,9 @@
      * or, if it does not fit, a truncated
      * copy with ellipsis character added at the specified edge or center.
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-                                         @NonNull TextPaint p,
-                                         @FloatRange(from = 0.0) float avail,
-                                         @NonNull TruncateAt where) {
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint p,
+                                         float avail, TruncateAt where) {
         return ellipsize(text, p, avail, where, false, null);
     }
 
@@ -1207,11 +1205,9 @@
      * report the start and end of the ellipsized range.  TextDirection
      * is determined by the first strong directional character.
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-                                         @NonNull TextPaint paint,
-                                         @FloatRange(from = 0.0) float avail,
-                                         @NonNull TruncateAt where,
+    public static CharSequence ellipsize(CharSequence text,
+                                         TextPaint paint,
+                                         float avail, TruncateAt where,
                                          boolean preserveLength,
                                          @Nullable EllipsizeCallback callback) {
         return ellipsize(text, paint, avail, where, preserveLength, callback,
@@ -1232,19 +1228,16 @@
      *
      * @hide
      */
-    @NonNull
-    public static CharSequence ellipsize(@NonNull CharSequence text,
-            @NonNull TextPaint paint,
-            @FloatRange(from = 0.0) float avail,
-            @NonNull TruncateAt where,
+    public static CharSequence ellipsize(CharSequence text,
+            TextPaint paint,
+            float avail, TruncateAt where,
             boolean preserveLength,
             @Nullable EllipsizeCallback callback,
-            @NonNull TextDirectionHeuristic textDir,
-            @NonNull String ellipsis) {
+            TextDirectionHeuristic textDir, String ellipsis) {
 
-        final int len = text.length();
-        final MeasuredText mt = MeasuredText.obtain();
-        MeasuredText resultMt = null;
+        int len = text.length();
+
+        MeasuredText mt = MeasuredText.obtain();
         try {
             float width = setPara(mt, paint, text, 0, text.length(), textDir);
 
@@ -1252,107 +1245,74 @@
                 if (callback != null) {
                     callback.ellipsized(0, 0);
                 }
+
                 return text;
             }
 
-            resultMt = MeasuredText.obtain();
-            // First estimate of effective width of ellipsis.
-            float ellipsisWidth = paint.measureText(ellipsis);
-            int numberOfTries = 0;
-            boolean textFits = false;
-            int start, end;
-            CharSequence result;
-            do {
-                if (avail < ellipsisWidth) {
-                    // Even the ellipsis can't fit. So it all goes.
-                    start = 0;
-                    end = len;
-                } else {
-                    final float remainingWidth = avail - ellipsisWidth;
-                    if (where == TruncateAt.START) {
-                        start = 0;
-                        end = len - mt.breakText(len, false /* backwards */, remainingWidth);
-                    } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
-                        start = mt.breakText(len, true /* forwards */, remainingWidth);
-                        end = len;
-                    } else {
-                        end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2);
-                        start = mt.breakText(end, true /* forwards */,
-                                remainingWidth - mt.measure(end, len));
-                    }
-                }
+            // XXX assumes ellipsis string does not require shaping and
+            // is unaffected by style
+            float ellipsiswid = paint.measureText(ellipsis);
+            avail -= ellipsiswid;
 
-                final char[] buf = mt.mChars;
-                final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
-
-                final int removed = end - start;
-                final int remaining = len - removed;
-                if (preserveLength) {
-                    int pos = start;
-                    if (remaining > 0 && removed >= ellipsis.length()) {
-                        ellipsis.getChars(0, ellipsis.length(), buf, start);
-                        pos += ellipsis.length();
-                    } // else eliminate the ellipsis
-                    while (pos < end) {
-                        buf[pos++] = ELLIPSIS_FILLER;
-                    }
-                    final String s = new String(buf, 0, len);
-                    if (sp == null) {
-                        result = s;
-                    } else {
-                        final SpannableString ss = new SpannableString(s);
-                        copySpansFrom(sp, 0, len, Object.class, ss, 0);
-                        result = ss;
-                    }
-                } else {
-                    if (remaining == 0) {
-                        result = "";
-                    } else if (sp == null) {
-                        final StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
-                        sb.append(buf, 0, start);
-                        sb.append(ellipsis);
-                        sb.append(buf, end, len - end);
-                        result = sb.toString();
-                    } else {
-                        final SpannableStringBuilder ssb = new SpannableStringBuilder();
-                        ssb.append(text, 0, start);
-                        ssb.append(ellipsis);
-                        ssb.append(text, end, len);
-                        result = ssb;
-                    }
-                }
-
-                if (remaining == 0) { // All text is gone.
-                    textFits = true;
-                } else {
-                    width = setPara(resultMt, paint, result, 0, result.length(), textDir);
-                    if (width <= avail) {
-                        textFits = true;
-                    } else {
-                        numberOfTries++;
-                        if (numberOfTries > 10) {
-                            // If the text still doesn't fit after ten tries, assume it will never
-                            // fit and ellipsize it all. We do this by setting the width of the
-                            // ellipsis to be positive infinity, so we get to empty text in the next
-                            // round.
-                            ellipsisWidth = Float.POSITIVE_INFINITY;
-                        } else {
-                            // Adjust the width of the ellipsis by adding the amount 'width' is
-                            // still over.
-                            ellipsisWidth += width - avail;
-                        }
-                    }
-                }
-            } while (!textFits);
-            if (callback != null) {
-                callback.ellipsized(start, end);
+            int left = 0;
+            int right = len;
+            if (avail < 0) {
+                // it all goes
+            } else if (where == TruncateAt.START) {
+                right = len - mt.breakText(len, false, avail);
+            } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
+                left = mt.breakText(len, true, avail);
+            } else {
+                right = len - mt.breakText(len, false, avail / 2);
+                avail -= mt.measure(right, len);
+                left = mt.breakText(right, true, avail);
             }
-            return result;
+
+            if (callback != null) {
+                callback.ellipsized(left, right);
+            }
+
+            char[] buf = mt.mChars;
+            Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+            final int removed = right - left;
+            final int remaining = len - removed;
+            if (preserveLength) {
+                if (remaining > 0 && removed >= ellipsis.length()) {
+                    ellipsis.getChars(0, ellipsis.length(), buf, left);
+                    left += ellipsis.length();
+                } // else skip the ellipsis
+                for (int i = left; i < right; i++) {
+                    buf[i] = ELLIPSIS_FILLER;
+                }
+                String s = new String(buf, 0, len);
+                if (sp == null) {
+                    return s;
+                }
+                SpannableString ss = new SpannableString(s);
+                copySpansFrom(sp, 0, len, Object.class, ss, 0);
+                return ss;
+            }
+
+            if (remaining == 0) {
+                return "";
+            }
+
+            if (sp == null) {
+                StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
+                sb.append(buf, 0, left);
+                sb.append(ellipsis);
+                sb.append(buf, right, len - right);
+                return sb.toString();
+            }
+
+            SpannableStringBuilder ssb = new SpannableStringBuilder();
+            ssb.append(text, 0, left);
+            ssb.append(ellipsis);
+            ssb.append(text, right, len);
+            return ssb;
         } finally {
             MeasuredText.recycle(mt);
-            if (resultMt != null) {
-                MeasuredText.recycle(resultMt);
-            }
         }
     }
 
@@ -1383,6 +1343,7 @@
      * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
      *     doesn't fit, it will return an empty string.
      */
+
     public static CharSequence listEllipsize(@Nullable Context context,
             @Nullable List<CharSequence> elements, @NonNull String separator,
             @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 48e5ca9..af26a88 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -193,6 +193,8 @@
     private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
         // check for user specified next focus
         View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
+        View cycleCheck = userSetNextFocus;
+        boolean cycleStep = true; // we want the first toggle to yield false
         while (userSetNextFocus != null) {
             if (userSetNextFocus.isFocusable()
                     && userSetNextFocus.getVisibility() == View.VISIBLE
@@ -201,6 +203,14 @@
                 return userSetNextFocus;
             }
             userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
+            if (cycleStep = !cycleStep) {
+                cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
+                if (cycleCheck == userSetNextFocus) {
+                    // found a cycle, user-specified focus forms a loop and none of the views
+                    // are currently focusable.
+                    break;
+                }
+            }
         }
         return null;
     }
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 20ab539..c566a65 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -111,9 +111,10 @@
      * to indicate whether the event was handled.  No new input events will be received
      * until {@link #finishInputEvent} is called.
      *
+     * @param displayId The display id on which input event triggered.
      * @param event The input event that was received.
      */
-    public void onInputEvent(InputEvent event) {
+    public void onInputEvent(InputEvent event, int displayId) {
         finishInputEvent(event, false);
     }
 
@@ -180,9 +181,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchInputEvent(int seq, InputEvent event) {
+    private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
         mSeqMap.put(event.getSequenceNumber(), seq);
-        onInputEvent(event);
+        onInputEvent(event, displayId);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 22329f4..44c88e1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -272,6 +272,15 @@
     public static final int POWER_MODE_DOZE_SUSPEND = 3;
 
     /**
+     * A value for windowType used to indicate that the window should be omitted from screenshots
+     * and display mirroring. A temporary workaround until we express such things with
+     * the hierarchy.
+     * TODO: b/64227542
+     * @hide
+     */
+    public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+
+    /**
      * Create a surface with a name.
      * <p>
      * The surface creation flags specify what kind of surface to create and
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 05f9da5..6bf4845 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6752,7 +6752,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             enqueueInputEvent(event, this, 0, true);
         }
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4041bcf..be763af 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1413,11 +1413,12 @@
         public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
 
         /**
-         * Flag to indicate that this window should be ignored when determining what parts of the
-         * screen can be magnified.
+         * Indicates that this window is the rounded corners overlay present on some
+         * devices this means that it will be excluded from: screenshots,
+         * screen magnification, and mirroring.
          * @hide
          */
-        public static final int PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT = 0x00100000;
+        public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
 
         /**
          * Control flags that are private to the platform.
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 4c9cf40..98f8dc8 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -347,4 +347,11 @@
      * Requests the window manager to recompute the windows for accessibility.
      */
     public abstract void computeWindowsForAccessibility();
+
+    /**
+     * Called after virtual display Id is updated by
+     * {@link com.android.server.vr.Vr2dDisplay} with a specific
+     * {@param vr2dDisplayId}.
+     */
+    public abstract void setVr2dDisplayId(int vr2dDisplayId);
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 668d25e..49b7ed8 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -616,7 +617,16 @@
          * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
          * copy() must be made and the copy must be recycled.
          **/
-        public void onPointerEvent(MotionEvent motionEvent);
+        void onPointerEvent(MotionEvent motionEvent);
+
+        /**
+         * @see #onPointerEvent(MotionEvent)
+         **/
+        default void onPointerEvent(MotionEvent motionEvent, int displayId) {
+            if (displayId == DEFAULT_DISPLAY) {
+                onPointerEvent(motionEvent);
+            }
+        }
     }
 
     /** Window has been added to the screen. */
diff --git a/core/java/android/view/textclassifier/LangId.java b/core/java/android/view/textclassifier/LangId.java
deleted file mode 100644
index 23c7842..0000000
--- a/core/java/android/view/textclassifier/LangId.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.view.textclassifier;
-
-/**
- *  Java wrapper for LangId native library interface.
- *  This class is used to detect languages in text.
- */
-final class LangId {
-
-    static {
-        System.loadLibrary("textclassifier");
-    }
-
-    private final long mModelPtr;
-
-    /**
-     * Creates a new instance of LangId predictor, using the provided model image.
-     */
-    LangId(int fd) {
-        mModelPtr = nativeNew(fd);
-    }
-
-    /**
-     * Detects the language for given text.
-     */
-    public ClassificationResult[] findLanguages(String text) {
-        return nativeFindLanguages(mModelPtr, text);
-    }
-
-    /**
-     * Frees up the allocated memory.
-     */
-    public void close() {
-        nativeClose(mModelPtr);
-    }
-
-    private static native long nativeNew(int fd);
-
-    private static native ClassificationResult[] nativeFindLanguages(
-            long context, String text);
-
-    private static native void nativeClose(long context);
-
-    /** Classification result for findLanguage method. */
-    static final class ClassificationResult {
-        final String mLanguage;
-        /** float range: 0 - 1 */
-        final float mScore;
-
-        ClassificationResult(String language, float score) {
-            mLanguage = language;
-            mScore = score;
-        }
-    }
-}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index efc88e2..d7b0776 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -16,37 +16,22 @@
 
 package android.view.textclassifier;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
 /**
  * Interface to the text classification service.
  */
 @SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
 public final class TextClassificationManager {
 
-    private static final String LOG_TAG = "TextClassificationManager";
-
     private final Object mTextClassifierLock = new Object();
-    private final Object mLangIdLock = new Object();
 
     private final Context mContext;
-    private ParcelFileDescriptor mLangIdFd;
     private TextClassifier mTextClassifier;
-    private LangId mLangId;
 
     /** @hide */
     public TextClassificationManager(Context context) {
@@ -75,47 +60,4 @@
             mTextClassifier = textClassifier;
         }
     }
-
-    /**
-     * Returns information containing languages that were detected in the provided text.
-     * This is a blocking operation you should avoid calling it on the UI thread.
-     *
-     * @throws IllegalArgumentException if text is null
-     * @hide
-     */
-    public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
-        Preconditions.checkArgument(text != null);
-        try {
-            if (text.length() > 0) {
-                final LangId.ClassificationResult[] results =
-                        getLanguageDetector().findLanguages(text.toString());
-                final TextLanguage.Builder tlBuilder = new TextLanguage.Builder(0, text.length());
-                final int size = results.length;
-                for (int i = 0; i < size; i++) {
-                    tlBuilder.setLanguage(
-                            new Locale.Builder().setLanguageTag(results[i].mLanguage).build(),
-                            results[i].mScore);
-                }
-
-                return Collections.unmodifiableList(Arrays.asList(tlBuilder.build()));
-            }
-        } catch (Throwable t) {
-            // Avoid throwing from this method. Log the error.
-            Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
-        }
-        // Getting here means something went wrong. Return an empty result.
-        return Collections.emptyList();
-    }
-
-    private LangId getLanguageDetector() throws FileNotFoundException {
-        synchronized (mLangIdLock) {
-            if (mLangId == null) {
-                mLangIdFd = ParcelFileDescriptor.open(
-                        new File("/etc/textclassifier/textclassifier.langid.model"),
-                        ParcelFileDescriptor.MODE_READ_ONLY);
-                mLangId = new LangId(mLangIdFd.getFd());
-            }
-            return mLangId;
-        }
-    }
 }
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
deleted file mode 100644
index 209813a..0000000
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.view.textclassifier;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- * @hide
- */
-public final class TextLanguage {
-
-    private final int mStartIndex;
-    private final int mEndIndex;
-    @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
-    @NonNull private final List<Locale> mLanguages;
-
-    private TextLanguage(
-            int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
-        mStartIndex = startIndex;
-        mEndIndex = endIndex;
-        mLanguageConfidence = new EntityConfidence<>(languageConfidence);
-        mLanguages = mLanguageConfidence.getEntities();
-    }
-
-    /**
-     * Returns the start index of the detected languages in the text provided to generate this
-     * object.
-     */
-    public int getStartIndex() {
-        return mStartIndex;
-    }
-
-    /**
-     * Returns the end index of the detected languages in the text provided to generate this object.
-     */
-    public int getEndIndex() {
-        return mEndIndex;
-    }
-
-    /**
-     * Returns the number of languages found in the classified text.
-     */
-    @IntRange(from = 0)
-    public int getLanguageCount() {
-        return mLanguages.size();
-    }
-
-    /**
-     * Returns the language locale at the specified index.
-     * Language locales are ordered from high confidence to low confidence.
-     *
-     * @throws IndexOutOfBoundsException if the specified index is out of range.
-     * @see #getLanguageCount() for the number of language locales available.
-     */
-    @NonNull
-    public Locale getLanguage(int index) {
-        return mLanguages.get(index);
-    }
-
-    /**
-     * Returns the confidence score for the specified language. The value ranges from
-     * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
-     * not found for the classified text.
-     */
-    @FloatRange(from = 0.0, to = 1.0)
-    public float getConfidenceScore(@Nullable Locale language) {
-        return mLanguageConfidence.getConfidenceScore(language);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("TextLanguage {%d, %d, %s}",
-                mStartIndex, mEndIndex, mLanguageConfidence);
-    }
-
-    /**
-     * Builder to build {@link TextLanguage} objects.
-     */
-    public static final class Builder {
-
-        private final int mStartIndex;
-        private final int mEndIndex;
-        @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
-                new EntityConfidence<>();
-
-        /**
-         * Creates a builder to build {@link TextLanguage} objects.
-         *
-         * @param startIndex the start index of the detected languages in the text provided
-         *      to generate the result
-         * @param endIndex the end index of the detected languages in the text provided
-         *      to generate the result. Must be greater than startIndex
-         */
-        public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
-            Preconditions.checkArgument(startIndex >= 0);
-            Preconditions.checkArgument(endIndex > startIndex);
-            mStartIndex = startIndex;
-            mEndIndex = endIndex;
-        }
-
-        /**
-         * Sets a language locale with the associated confidence score.
-         */
-        public Builder setLanguage(
-                @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
-            mLanguageConfidence.setEntityType(locale, confidenceScore);
-            return this;
-        }
-
-        /**
-         * Builds and returns a {@link TextLanguage}.
-         */
-        public TextLanguage build() {
-            return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
-        }
-    }
-}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1291671..bbfa6e0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -119,6 +119,7 @@
 import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.view.AccessibilityIterators.TextSegmentIterator;
 import android.view.ActionMode;
@@ -855,22 +856,9 @@
 
         mTransformation = null;
 
-        int textColorHighlight = 0;
-        ColorStateList textColor = null;
-        ColorStateList textColorHint = null;
-        ColorStateList textColorLink = null;
-        int textSize = 15;
-        String fontFamily = null;
-        Typeface fontTypeface = null;
-        boolean fontFamilyExplicit = false;
-        int typefaceIndex = -1;
-        int styleIndex = -1;
-        boolean allCaps = false;
-        int shadowcolor = 0;
-        float dx = 0, dy = 0, r = 0;
-        boolean elegant = false;
-        float letterSpacing = 0;
-        String fontFeatureSettings = null;
+        final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
+        attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
+        attributes.mTextSize = 15;
         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
@@ -894,87 +882,8 @@
                     ap, com.android.internal.R.styleable.TextAppearance);
         }
         if (appearance != null) {
-            int n = appearance.getIndexCount();
-            for (int i = 0; i < n; i++) {
-                int attr = appearance.getIndex(i);
-
-                switch (attr) {
-                    case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
-                        textColorHighlight = appearance.getColor(attr, textColorHighlight);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textColor:
-                        textColor = appearance.getColorStateList(attr);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textColorHint:
-                        textColorHint = appearance.getColorStateList(attr);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textColorLink:
-                        textColorLink = appearance.getColorStateList(attr);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textSize:
-                        textSize = appearance.getDimensionPixelSize(attr, textSize);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_typeface:
-                        typefaceIndex = appearance.getInt(attr, -1);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_fontFamily:
-                        if (!context.isRestricted() && context.canLoadUnsafeResources()) {
-                            try {
-                                fontTypeface = appearance.getFont(attr);
-                            } catch (UnsupportedOperationException
-                                    | Resources.NotFoundException e) {
-                                // Expected if it is not a font resource.
-                            }
-                        }
-                        if (fontTypeface == null) {
-                            fontFamily = appearance.getString(attr);
-                        }
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textStyle:
-                        styleIndex = appearance.getInt(attr, -1);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_textAllCaps:
-                        allCaps = appearance.getBoolean(attr, false);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_shadowColor:
-                        shadowcolor = appearance.getInt(attr, 0);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_shadowDx:
-                        dx = appearance.getFloat(attr, 0);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_shadowDy:
-                        dy = appearance.getFloat(attr, 0);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_shadowRadius:
-                        r = appearance.getFloat(attr, 0);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
-                        elegant = appearance.getBoolean(attr, false);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_letterSpacing:
-                        letterSpacing = appearance.getFloat(attr, 0);
-                        break;
-
-                    case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
-                        fontFeatureSettings = appearance.getString(attr);
-                        break;
-                }
-            }
-
+            readTextAppearance(context, appearance, attributes, false /* styleArray */);
+            attributes.mFontFamilyExplicit = false;
             appearance.recycle();
         }
 
@@ -1005,6 +914,8 @@
         a = theme.obtainStyledAttributes(
                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
 
+        readTextAppearance(context, a, attributes, true /* styleArray */);
+
         int n = a.getIndexCount();
 
         boolean fromResourceId = false;
@@ -1195,69 +1106,10 @@
                     mFreezesText = a.getBoolean(attr, false);
                     break;
 
-                case com.android.internal.R.styleable.TextView_shadowColor:
-                    shadowcolor = a.getInt(attr, 0);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_shadowDx:
-                    dx = a.getFloat(attr, 0);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_shadowDy:
-                    dy = a.getFloat(attr, 0);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_shadowRadius:
-                    r = a.getFloat(attr, 0);
-                    break;
-
                 case com.android.internal.R.styleable.TextView_enabled:
                     setEnabled(a.getBoolean(attr, isEnabled()));
                     break;
 
-                case com.android.internal.R.styleable.TextView_textColorHighlight:
-                    textColorHighlight = a.getColor(attr, textColorHighlight);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_textColor:
-                    textColor = a.getColorStateList(attr);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_textColorHint:
-                    textColorHint = a.getColorStateList(attr);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_textColorLink:
-                    textColorLink = a.getColorStateList(attr);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_textSize:
-                    textSize = a.getDimensionPixelSize(attr, textSize);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_typeface:
-                    typefaceIndex = a.getInt(attr, typefaceIndex);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_textStyle:
-                    styleIndex = a.getInt(attr, styleIndex);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_fontFamily:
-                    if (!context.isRestricted() && context.canLoadUnsafeResources()) {
-                        try {
-                            fontTypeface = a.getFont(attr);
-                        } catch (UnsupportedOperationException | Resources.NotFoundException e) {
-                            // Expected if it is not a resource reference or if it is a reference to
-                            // another resource type.
-                        }
-                    }
-                    if (fontTypeface == null) {
-                        fontFamily = a.getString(attr);
-                    }
-                    fontFamilyExplicit = true;
-                    break;
-
                 case com.android.internal.R.styleable.TextView_password:
                     password = a.getBoolean(attr, password);
                     break;
@@ -1345,22 +1197,6 @@
                     setTextIsSelectable(a.getBoolean(attr, false));
                     break;
 
-                case com.android.internal.R.styleable.TextView_textAllCaps:
-                    allCaps = a.getBoolean(attr, false);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_elegantTextHeight:
-                    elegant = a.getBoolean(attr, false);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_letterSpacing:
-                    letterSpacing = a.getFloat(attr, 0);
-                    break;
-
-                case com.android.internal.R.styleable.TextView_fontFeatureSettings:
-                    fontFeatureSettings = a.getString(attr);
-                    break;
-
                 case com.android.internal.R.styleable.TextView_breakStrategy:
                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
                     break;
@@ -1592,38 +1428,20 @@
                 break;
         }
 
-        setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
-        setHintTextColor(textColorHint);
-        setLinkTextColor(textColorLink);
-        if (textColorHighlight != 0) {
-            setHighlightColor(textColorHighlight);
-        }
-        setRawTextSize(textSize, true /* shouldRequestLayout */);
-        setElegantTextHeight(elegant);
-        setLetterSpacing(letterSpacing);
-        setFontFeatureSettings(fontFeatureSettings);
-
-        if (allCaps) {
-            setTransformationMethod(new AllCapsTransformationMethod(getContext()));
-        }
-
-        if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
-            setTransformationMethod(PasswordTransformationMethod.getInstance());
-            typefaceIndex = MONOSPACE;
-        } else if (mEditor != null
+        final boolean isPassword = password || passwordInputType || webPasswordInputType
+                || numberPasswordInputType;
+        final boolean isMonospaceEnforced = isPassword || (mEditor != null
                 && (mEditor.mInputType
-                        & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
-                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
-            typefaceIndex = MONOSPACE;
+                & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
+                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
+        if (isMonospaceEnforced) {
+            attributes.mTypefaceIndex = MONOSPACE;
         }
 
-        if (typefaceIndex != -1 && !fontFamilyExplicit) {
-            fontFamily = null;
-        }
-        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
+        applyTextAppearance(attributes);
 
-        if (shadowcolor != 0) {
-            setShadowLayer(r, dx, dy, shadowcolor);
+        if (isPassword) {
+            setTransformationMethod(PasswordTransformationMethod.getInstance());
         }
 
         if (maxlength >= 0) {
@@ -3397,79 +3215,244 @@
     @Deprecated
     public void setTextAppearance(Context context, @StyleRes int resId) {
         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
+        final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
+        readTextAppearance(context, ta, attributes, false /* styleArray */);
+        ta.recycle();
+        applyTextAppearance(attributes);
+    }
 
-        final int textColorHighlight = ta.getColor(
-                R.styleable.TextAppearance_textColorHighlight, 0);
-        if (textColorHighlight != 0) {
-            setHighlightColor(textColorHighlight);
+    /**
+     * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
+     * that reads these attributes in the constructor and in {@link #setTextAppearance}.
+     */
+    private static class TextAppearanceAttributes {
+        int mTextColorHighlight = 0;
+        ColorStateList mTextColor = null;
+        ColorStateList mTextColorHint = null;
+        ColorStateList mTextColorLink = null;
+        int mTextSize = 0;
+        String mFontFamily = null;
+        Typeface mFontTypeface = null;
+        boolean mFontFamilyExplicit = false;
+        int mTypefaceIndex = -1;
+        int mStyleIndex = -1;
+        boolean mAllCaps = false;
+        int mShadowColor = 0;
+        float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
+        boolean mHasElegant = false;
+        boolean mElegant = false;
+        boolean mHasLetterSpacing = false;
+        float mLetterSpacing = 0;
+        String mFontFeatureSettings = null;
+
+        @Override
+        public String toString() {
+            return "TextAppearanceAttributes {\n"
+                    + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
+                    + "    mTextColor:" + mTextColor + "\n"
+                    + "    mTextColorHint:" + mTextColorHint + "\n"
+                    + "    mTextColorLink:" + mTextColorLink + "\n"
+                    + "    mTextSize:" + mTextSize + "\n"
+                    + "    mFontFamily:" + mFontFamily + "\n"
+                    + "    mFontTypeface:" + mFontTypeface + "\n"
+                    + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
+                    + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
+                    + "    mStyleIndex:" + mStyleIndex + "\n"
+                    + "    mAllCaps:" + mAllCaps + "\n"
+                    + "    mShadowColor:" + mShadowColor + "\n"
+                    + "    mShadowDx:" + mShadowDx + "\n"
+                    + "    mShadowDy:" + mShadowDy + "\n"
+                    + "    mShadowRadius:" + mShadowRadius + "\n"
+                    + "    mHasElegant:" + mHasElegant + "\n"
+                    + "    mElegant:" + mElegant + "\n"
+                    + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
+                    + "    mLetterSpacing:" + mLetterSpacing + "\n"
+                    + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
+                    + "}";
         }
+    }
 
-        final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
-        if (textColor != null) {
-            setTextColor(textColor);
-        }
+    // Maps styleable attributes that exist both in TextView style and TextAppearance.
+    private static final SparseIntArray sAppearanceValues = new SparseIntArray();
+    static {
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
+                com.android.internal.R.styleable.TextAppearance_textColorHighlight);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
+                com.android.internal.R.styleable.TextAppearance_textColor);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
+                com.android.internal.R.styleable.TextAppearance_textColorHint);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
+                com.android.internal.R.styleable.TextAppearance_textColorLink);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
+                com.android.internal.R.styleable.TextAppearance_textSize);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
+                com.android.internal.R.styleable.TextAppearance_typeface);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
+                com.android.internal.R.styleable.TextAppearance_fontFamily);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
+                com.android.internal.R.styleable.TextAppearance_textStyle);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
+                com.android.internal.R.styleable.TextAppearance_textAllCaps);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
+                com.android.internal.R.styleable.TextAppearance_shadowColor);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
+                com.android.internal.R.styleable.TextAppearance_shadowDx);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
+                com.android.internal.R.styleable.TextAppearance_shadowDy);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
+                com.android.internal.R.styleable.TextAppearance_shadowRadius);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
+                com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
+                com.android.internal.R.styleable.TextAppearance_letterSpacing);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
+                com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
+    }
 
-        final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
-        if (textSize != 0) {
-            setRawTextSize(textSize, true /* shouldRequestLayout */);
-        }
-
-        final ColorStateList textColorHint = ta.getColorStateList(
-                R.styleable.TextAppearance_textColorHint);
-        if (textColorHint != null) {
-            setHintTextColor(textColorHint);
-        }
-
-        final ColorStateList textColorLink = ta.getColorStateList(
-                R.styleable.TextAppearance_textColorLink);
-        if (textColorLink != null) {
-            setLinkTextColor(textColorLink);
-        }
-
-        Typeface fontTypeface = null;
-        String fontFamily = null;
-        if (!context.isRestricted() && context.canLoadUnsafeResources()) {
-            try {
-                fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
-            } catch (UnsupportedOperationException | Resources.NotFoundException e) {
-                // Expected if it is not a font resource.
+    /**
+     * Read the Text Appearance attributes from a given TypedArray and set its values to the given
+     * set. If the TypedArray contains a value that was already set in the given attributes, that
+     * will be overriden.
+     *
+     * @param context The Context to be used
+     * @param appearance The TypedArray to read properties from
+     * @param attributes the TextAppearanceAttributes to fill in
+     * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
+     *                   what attribute indexes will be used to read the properties.
+     */
+    private void readTextAppearance(Context context, TypedArray appearance,
+            TextAppearanceAttributes attributes, boolean styleArray) {
+        final int n = appearance.getIndexCount();
+        for (int i = 0; i < n; i++) {
+            final int attr = appearance.getIndex(i);
+            int index = attr;
+            // Translate style array index ids to TextAppearance ids.
+            if (styleArray) {
+                index = sAppearanceValues.get(attr, -1);
+                if (index == -1) {
+                    // This value is not part of a Text Appearance and should be ignored.
+                    continue;
+                }
+            }
+            switch (index) {
+                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
+                    attributes.mTextColorHighlight =
+                            appearance.getColor(attr, attributes.mTextColorHighlight);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textColor:
+                    attributes.mTextColor = appearance.getColorStateList(attr);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textColorHint:
+                    attributes.mTextColorHint = appearance.getColorStateList(attr);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textColorLink:
+                    attributes.mTextColorLink = appearance.getColorStateList(attr);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textSize:
+                    attributes.mTextSize =
+                            appearance.getDimensionPixelSize(attr, attributes.mTextSize);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_typeface:
+                    attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
+                    if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
+                        attributes.mFontFamily = null;
+                    }
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_fontFamily:
+                    if (!context.isRestricted() && context.canLoadUnsafeResources()) {
+                        try {
+                            attributes.mFontTypeface = appearance.getFont(attr);
+                        } catch (UnsupportedOperationException | Resources.NotFoundException e) {
+                            // Expected if it is not a font resource.
+                        }
+                    }
+                    if (attributes.mFontTypeface == null) {
+                        attributes.mFontFamily = appearance.getString(attr);
+                    }
+                    attributes.mFontFamilyExplicit = true;
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textStyle:
+                    attributes.mStyleIndex = appearance.getInt(attr, attributes.mStyleIndex);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_textAllCaps:
+                    attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_shadowColor:
+                    attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_shadowDx:
+                    attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_shadowDy:
+                    attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_shadowRadius:
+                    attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
+                    attributes.mHasElegant = true;
+                    attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_letterSpacing:
+                    attributes.mHasLetterSpacing = true;
+                    attributes.mLetterSpacing =
+                            appearance.getFloat(attr, attributes.mLetterSpacing);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
+                    attributes.mFontFeatureSettings = appearance.getString(attr);
+                    break;
+                default:
             }
         }
-        if (fontTypeface == null) {
-            fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
-        }
-        final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
-        final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
-        setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
+    }
 
-        final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
-        if (shadowColor != 0) {
-            final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
-            final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
-            final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
-            setShadowLayer(r, dx, dy, shadowColor);
+    private void applyTextAppearance(TextAppearanceAttributes attributes) {
+        if (attributes.mTextColor != null) {
+            setTextColor(attributes.mTextColor);
         }
 
-        if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
+        if (attributes.mTextColorHint != null) {
+            setHintTextColor(attributes.mTextColorHint);
+        }
+
+        if (attributes.mTextColorLink != null) {
+            setLinkTextColor(attributes.mTextColorLink);
+        }
+
+        if (attributes.mTextColorHighlight != 0) {
+            setHighlightColor(attributes.mTextColorHighlight);
+        }
+
+        if (attributes.mTextSize != 0) {
+            setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
+        }
+
+        if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
+            attributes.mFontFamily = null;
+        }
+        setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
+                attributes.mTypefaceIndex, attributes.mStyleIndex);
+
+        if (attributes.mShadowColor != 0) {
+            setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
+                    attributes.mShadowColor);
+        }
+
+        if (attributes.mAllCaps) {
             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
         }
 
-        if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
-            setElegantTextHeight(ta.getBoolean(
-                    R.styleable.TextAppearance_elegantTextHeight, false));
+        if (attributes.mHasElegant) {
+            setElegantTextHeight(attributes.mElegant);
         }
 
-        if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
-            setLetterSpacing(ta.getFloat(
-                    R.styleable.TextAppearance_letterSpacing, 0));
+        if (attributes.mHasLetterSpacing) {
+            setLetterSpacing(attributes.mLetterSpacing);
         }
 
-        if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
-            setFontFeatureSettings(ta.getString(
-                    R.styleable.TextAppearance_fontFeatureSettings));
+        if (attributes.mFontFeatureSettings != null) {
+            setFontFeatureSettings(attributes.mFontFeatureSettings);
         }
-
-        ta.recycle();
     }
 
     /**
@@ -3744,6 +3727,8 @@
      *
      * @param elegant set the paint's elegant metrics flag.
      *
+     * @see Paint#isElegantTextHeight(boolean)
+     *
      * @attr ref android.R.styleable#TextView_elegantTextHeight
      */
     public void setElegantTextHeight(boolean elegant) {
@@ -3758,6 +3743,19 @@
     }
 
     /**
+     * Get the value of the TextView's elegant height metrics flag. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical
+     * metrics, and also increases top and bottom bounds to provide more space.
+     * @return {@code true} if the elegant height metrics flag is set.
+     *
+     * @see #setElegantTextHeight(boolean)
+     * @see Paint#setElegantTextHeight(boolean)
+     */
+    public boolean isElegantTextHeight() {
+        return mTextPaint.isElegantTextHeight();
+    }
+
+    /**
      * Gets the text letter-space value, which determines the spacing between characters.
      * The value returned is in ems. Normally, this value is 0.0.
      * @return The text letter-space value in ems.
@@ -4946,20 +4944,21 @@
 
     private void updateTextColors() {
         boolean inval = false;
-        int color = mTextColor.getColorForState(getDrawableState(), 0);
+        final int[] drawableState = getDrawableState();
+        int color = mTextColor.getColorForState(drawableState, 0);
         if (color != mCurTextColor) {
             mCurTextColor = color;
             inval = true;
         }
         if (mLinkTextColor != null) {
-            color = mLinkTextColor.getColorForState(getDrawableState(), 0);
+            color = mLinkTextColor.getColorForState(drawableState, 0);
             if (color != mTextPaint.linkColor) {
                 mTextPaint.linkColor = color;
                 inval = true;
             }
         }
         if (mHintTextColor != null) {
-            color = mHintTextColor.getColorForState(getDrawableState(), 0);
+            color = mHintTextColor.getColorForState(drawableState, 0);
             if (color != mCurHintTextColor) {
                 mCurHintTextColor = color;
                 if (mText.length() == 0) {
@@ -8348,7 +8347,9 @@
     }
 
     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
-        final CharSequence text = getText();
+        final CharSequence text = mTransformed != null
+                ? mTransformed
+                : getText();
         final int maxLines = getMaxLines();
         if (mTempTextPaint == null) {
             mTempTextPaint = new TextPaint();
@@ -9022,6 +9023,20 @@
     }
 
     /**
+     *
+     * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This
+     * settings is internally ignored if this field is editable or selectable.
+     * @return Whether the current transformation method is for ALL CAPS.
+     *
+     * @see #setAllCaps(boolean)
+     * @see #setTransformationMethod(TransformationMethod)
+     */
+    public boolean isAllCaps() {
+        final TransformationMethod method = getTransformationMethod();
+        return method != null && method instanceof AllCapsTransformationMethod;
+    }
+
+    /**
      * If true, sets the properties of this field (number of lines, horizontally scrolling,
      * transformation method) to be for a single-line input; if false, restores these to the default
      * conditions.
diff --git a/core/java/com/android/internal/colorextraction/ColorExtractor.java b/core/java/com/android/internal/colorextraction/ColorExtractor.java
index 727412b..ef98a5e9 100644
--- a/core/java/com/android/internal/colorextraction/ColorExtractor.java
+++ b/core/java/com/android/internal/colorextraction/ColorExtractor.java
@@ -22,6 +22,7 @@
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -44,6 +45,7 @@
     private static final int[] sGradientTypes = new int[]{TYPE_NORMAL, TYPE_DARK, TYPE_EXTRA_DARK};
 
     private static final String TAG = "ColorExtractor";
+    private static final boolean DEBUG = false;
 
     protected final SparseArray<GradientColors[]> mGradientColors;
     private final ArrayList<WeakReference<OnColorsChangedListener>> mOnColorsChangedListeners;
@@ -147,6 +149,9 @@
 
     @Override
     public void onColorsChanged(WallpaperColors colors, int which) {
+        if (DEBUG) {
+            Log.d(TAG, "New wallpaper colors for " + which + ": " + colors);
+        }
         boolean changed = false;
         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
             mLockColors = colors;
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index d765fd2..d49d572 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -38,6 +38,7 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
+import android.provider.MetadataReader;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -51,6 +52,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -118,26 +120,31 @@
 
         if (!file.isFile()) {
             Log.w(TAG, "Can't stream non-regular file. Returning empty metadata.");
-            return Bundle.EMPTY;
+            return null;
         }
 
         if (!file.canRead()) {
             Log.w(TAG, "Can't stream non-readable file. Returning empty metadata.");
-            return Bundle.EMPTY;
+            return null;
         }
 
-        String filePath = file.getAbsolutePath();
-        FileInputStream stream = new FileInputStream(filePath);
+        String mimeType = getTypeForFile(file);
+        if (!MetadataReader.isSupportedMimeType(mimeType)) {
+            return null;
+        }
 
+        InputStream stream = null;
         try {
-            return getDocumentMetadataFromStream(stream, getTypeForFile(file));
+            Bundle metadata = new Bundle();
+            stream = new FileInputStream(file.getAbsolutePath());
+            MetadataReader.getMetadata(metadata, stream, mimeType, null);
+            return metadata;
         } catch (IOException e) {
             Log.e(TAG, "An error occurred retrieving the metadata", e);
+            return null;
         } finally {
             IoUtils.closeQuietly(stream);
         }
-
-        return null;
     }
 
     protected final List<String> findDocumentPath(File parent, File doc)
@@ -491,7 +498,7 @@
     }
 
     protected boolean typeSupportsMetadata(String mimeType) {
-        return MIMETYPE_JPG.equals(mimeType) || MIMETYPE_JPEG.equals(mimeType);
+        return MetadataReader.isSupportedMimeType(mimeType);
     }
 
     private static String getTypeForName(String name) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index d7ecaec..0700411 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -157,6 +157,15 @@
     // Number of transmit power states the Bluetooth controller can be in.
     private static final int NUM_BT_TX_LEVELS = 1;
 
+    /**
+     * Holding a wakelock costs more than just using the cpu.
+     * Currently, we assign only half the cpu time to an app that is running but
+     * not holding a wakelock. The apps holding wakelocks get the rest of the blame.
+     * If no app is holding a wakelock, then the distribution is normal.
+     */
+    @VisibleForTesting
+    public static final int WAKE_LOCK_WEIGHT = 50;
+
     protected Clocks mClocks;
 
     private final JournaledFile mFile;
@@ -171,9 +180,12 @@
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
-    private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
-    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
-    private final KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
+    @VisibleForTesting
+    protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+    @VisibleForTesting
+    protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+    @VisibleForTesting
+    protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
             new KernelUidCpuFreqTimeReader();
 
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
@@ -197,10 +209,12 @@
     public static abstract class UserInfoProvider {
         private int[] userIds;
         protected abstract @Nullable int[] getUserIds();
-        private final void refreshUserIds() {
+        @VisibleForTesting
+        public final void refreshUserIds() {
             userIds = getUserIds();
         }
-        private final boolean exists(int userId) {
+        @VisibleForTesting
+        public boolean exists(int userId) {
             return userIds != null ? ArrayUtils.contains(userIds, userId) : true;
         }
     }
@@ -273,7 +287,8 @@
 
     public final MyHandler mHandler;
     private ExternalStatsSync mExternalSync = null;
-    private UserInfoProvider mUserInfoProvider = null;
+    @VisibleForTesting
+    protected UserInfoProvider mUserInfoProvider = null;
 
     private BatteryCallback mCallback;
 
@@ -291,7 +306,8 @@
     // elapsed time by the number of active timers to arrive at that timer's share of the time.
     // In order to do this, we must refresh each timer whenever the number of active timers
     // changes.
-    final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
+    @VisibleForTesting
+    protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
     final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
     final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
     final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
@@ -308,7 +324,8 @@
     final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
 
     // Last partial timers we use for distributing CPU usage.
-    final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
+    @VisibleForTesting
+    protected ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
 
     // These are the objects that will want to do something when the device
     // is unplugged from power.
@@ -536,7 +553,8 @@
      * in to power.
      */
     boolean mOnBattery;
-    boolean mOnBatteryInternal;
+    @VisibleForTesting
+    protected boolean mOnBatteryInternal;
 
     /**
      * External reporting of whether the device is actually charging.
@@ -606,7 +624,8 @@
 
     private long[] mCpuFreqs;
 
-    private PowerProfile mPowerProfile;
+    @VisibleForTesting
+    protected PowerProfile mPowerProfile;
 
     /*
      * Holds a SamplingTimer associated with each kernel wakelock name being tracked.
@@ -2020,7 +2039,8 @@
          * For partial wake locks, keep track of whether we are in the list
          * to consume CPU cycles.
          */
-        boolean mInList;
+        @VisibleForTesting
+        public boolean mInList;
 
         public StopwatchTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
                 TimeBase timeBase, Parcel in) {
@@ -3632,7 +3652,12 @@
     }
 
     public void noteUidProcessStateLocked(int uid, int state) {
-        uid = mapUid(uid);
+        int parentUid = mapUid(uid);
+        if (uid != parentUid) {
+            // Isolated UIDs process state is already rolled up into parent, so no need to track
+            // Otherwise the parent's process state will get downgraded incorrectly
+            return;
+        }
         getUidStatsLocked(uid).updateUidProcessStateLocked(state);
     }
 
@@ -10262,169 +10287,83 @@
             Slog.d(TAG, "!Cpu updating!");
         }
 
-        // Holding a wakelock costs more than just using the cpu.
-        // Currently, we assign only half the cpu time to an app that is running but
-        // not holding a wakelock. The apps holding wakelocks get the rest of the blame.
-        // If no app is holding a wakelock, then the distribution is normal.
-        final int wakelockWeight = 50;
-
-        int numWakelocks = 0;
-
-        // Calculate how many wakelocks we have to distribute amongst. The system is excluded.
-        // Only distribute cpu power to wakelocks if the screen is off and we're on battery.
-        final int numPartialTimers = mPartialTimers.size();
+        // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
+        // usually holding the wakelock on behalf of an app.
+        // And Only distribute cpu power to wakelocks if the screen is off and we're on battery.
+        ArrayList<StopwatchTimer> partialTimersToConsider = null;
         if (mOnBatteryScreenOffTimeBase.isRunning()) {
-            for (int i = 0; i < numPartialTimers; i++) {
+            partialTimersToConsider = new ArrayList<>();
+            for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
                 final StopwatchTimer timer = mPartialTimers.get(i);
+                // Since the collection and blaming of wakelocks can be scheduled to run after
+                // some delay, the mPartialTimers list may have new entries. We can't blame
+                // the newly added timer for past cpu time, so we only consider timers that
+                // were present for one round of collection. Once a timer has gone through
+                // a round of collection, its mInList field is set to true.
                 if (timer.mInList && timer.mUid != null && timer.mUid.mUid != Process.SYSTEM_UID) {
-                    // Since the collection and blaming of wakelocks can be scheduled to run after
-                    // some delay, the mPartialTimers list may have new entries. We can't blame
-                    // the newly added timer for past cpu time, so we only consider timers that
-                    // were present for one round of collection. Once a timer has gone through
-                    // a round of collection, its mInList field is set to true.
-                    numWakelocks++;
+                    partialTimersToConsider.add(timer);
                 }
             }
         }
+        markPartialTimersAsEligible();
 
-        final int numWakelocksF = numWakelocks;
-        mTempTotalCpuUserTimeUs = 0;
-        mTempTotalCpuSystemTimeUs = 0;
+        // When the battery is not on, we don't attribute the cpu times to any timers but we still
+        // need to take the snapshots.
+        if (!mOnBatteryInternal) {
+            mKernelUidCpuTimeReader.readDelta(null);
+            mKernelUidCpuFreqTimeReader.readDelta(null);
+            for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
+                mKernelCpuSpeedReaders[cluster].readDelta();
+            }
+            return;
+        }
 
-        final SparseLongArray updatedUids = new SparseLongArray();
-
-        // Read the CPU data for each UID. This will internally generate a snapshot so next time
-        // we read, we get a delta. If we are to distribute the cpu time, then do so. Otherwise
-        // we just ignore the data.
-        final long startTimeMs = mClocks.uptimeMillis();
         mUserInfoProvider.refreshUserIds();
-        mKernelUidCpuTimeReader.readDelta(!mOnBatteryInternal ? null :
-                new KernelUidCpuTimeReader.Callback() {
-                    @Override
-                    public void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs) {
-                        uid = mapUid(uid);
-                        if (Process.isIsolated(uid)) {
-                            // This could happen if the isolated uid mapping was removed before
-                            // that process was actually killed.
-                            mKernelUidCpuTimeReader.removeUid(uid);
-                            Slog.d(TAG, "Got readings for an isolated uid with"
-                                    + " no mapping to owning uid: " + uid);
-                            return;
-                        }
-                        if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
-                            Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
-                            mKernelUidCpuTimeReader.removeUid(uid);
-                            return;
-                        }
-                        final Uid u = getUidStatsLocked(uid);
-
-                        // Accumulate the total system and user time.
-                        mTempTotalCpuUserTimeUs += userTimeUs;
-                        mTempTotalCpuSystemTimeUs += systemTimeUs;
-
-                        StringBuilder sb = null;
-                        if (DEBUG_ENERGY_CPU) {
-                            sb = new StringBuilder();
-                            sb.append("  got time for uid=").append(u.mUid).append(": u=");
-                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
-                            sb.append(" s=");
-                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
-                            sb.append("\n");
-                        }
-
-                        if (numWakelocksF > 0) {
-                            // We have wakelocks being held, so only give a portion of the
-                            // time to the process. The rest will be distributed among wakelock
-                            // holders.
-                            userTimeUs = (userTimeUs * wakelockWeight) / 100;
-                            systemTimeUs = (systemTimeUs * wakelockWeight) / 100;
-                        }
-
-                        if (sb != null) {
-                            sb.append("  adding to uid=").append(u.mUid).append(": u=");
-                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
-                            sb.append(" s=");
-                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
-                            Slog.d(TAG, sb.toString());
-                        }
-
-                        u.mUserCpuTime.addCountLocked(userTimeUs);
-                        u.mSystemCpuTime.addCountLocked(systemTimeUs);
-                        updatedUids.put(u.getUid(), userTimeUs + systemTimeUs);
-                    }
-                });
-
+        final SparseLongArray updatedUids = new SparseLongArray();
+        readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids);
+        updateClusterSpeedTimes(updatedUids);
         if (updateCpuFreqData) {
             readKernelUidCpuFreqTimesLocked();
         }
+    }
 
-        final long elapse = (mClocks.uptimeMillis() - startTimeMs);
-        if (DEBUG_ENERGY_CPU || (elapse >= 100)) {
-            Slog.d(TAG, "Reading cpu stats took " + elapse + " ms");
-        }
+    /**
+     * Mark the current partial timers as gone through a collection so that they will be
+     * considered in the next cpu times distribution to wakelock holders.
+     */
+    @VisibleForTesting
+    public void markPartialTimersAsEligible() {
+        if (ArrayUtils.referenceEquals(mPartialTimers, mLastPartialTimers)) {
+            // No difference, so each timer is now considered for the next collection.
+            for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
+                mPartialTimers.get(i).mInList = true;
+            }
+        } else {
+            // The lists are different, meaning we added (or removed a timer) since the last
+            // collection.
+            for (int i = mLastPartialTimers.size() - 1; i >= 0; --i) {
+                mLastPartialTimers.get(i).mInList = false;
+            }
+            mLastPartialTimers.clear();
 
-        if (mOnBatteryInternal && numWakelocks > 0) {
-            // Distribute a portion of the total cpu time to wakelock holders.
-            mTempTotalCpuUserTimeUs = (mTempTotalCpuUserTimeUs * (100 - wakelockWeight)) / 100;
-            mTempTotalCpuSystemTimeUs =
-                    (mTempTotalCpuSystemTimeUs * (100 - wakelockWeight)) / 100;
-
-            for (int i = 0; i < numPartialTimers; i++) {
+            // Mark the current timers as gone through a collection.
+            final int numPartialTimers = mPartialTimers.size();
+            for (int i = 0; i < numPartialTimers; ++i) {
                 final StopwatchTimer timer = mPartialTimers.get(i);
-
-                // The system does not share any blame, as it is usually holding the wakelock
-                // on behalf of an app.
-                if (timer.mInList && timer.mUid != null && timer.mUid.mUid != Process.SYSTEM_UID) {
-                    int userTimeUs = (int) (mTempTotalCpuUserTimeUs / numWakelocks);
-                    int systemTimeUs = (int) (mTempTotalCpuSystemTimeUs / numWakelocks);
-
-                    if (DEBUG_ENERGY_CPU) {
-                        StringBuilder sb = new StringBuilder();
-                        sb.append("  Distributing wakelock uid=").append(timer.mUid.mUid)
-                                .append(": u=");
-                        TimeUtils.formatDuration(userTimeUs / 1000, sb);
-                        sb.append(" s=");
-                        TimeUtils.formatDuration(systemTimeUs / 1000, sb);
-                        Slog.d(TAG, sb.toString());
-                    }
-
-                    timer.mUid.mUserCpuTime.addCountLocked(userTimeUs);
-                    timer.mUid.mSystemCpuTime.addCountLocked(systemTimeUs);
-                    final int uid = timer.mUid.getUid();
-                    updatedUids.put(uid, updatedUids.get(uid, 0) + userTimeUs + systemTimeUs);
-
-                    final Uid.Proc proc = timer.mUid.getProcessStatsLocked("*wakelock*");
-                    proc.addCpuTimeLocked(userTimeUs / 1000, systemTimeUs / 1000);
-
-                    mTempTotalCpuUserTimeUs -= userTimeUs;
-                    mTempTotalCpuSystemTimeUs -= systemTimeUs;
-                    numWakelocks--;
-                }
-            }
-
-            if (mTempTotalCpuUserTimeUs > 0 || mTempTotalCpuSystemTimeUs > 0) {
-                // Anything left over is given to the system.
-                if (DEBUG_ENERGY_CPU) {
-                    StringBuilder sb = new StringBuilder();
-                    sb.append("  Distributing lost time to system: u=");
-                    TimeUtils.formatDuration(mTempTotalCpuUserTimeUs / 1000, sb);
-                    sb.append(" s=");
-                    TimeUtils.formatDuration(mTempTotalCpuSystemTimeUs / 1000, sb);
-                    Slog.d(TAG, sb.toString());
-                }
-
-                final Uid u = getUidStatsLocked(Process.SYSTEM_UID);
-                u.mUserCpuTime.addCountLocked(mTempTotalCpuUserTimeUs);
-                u.mSystemCpuTime.addCountLocked(mTempTotalCpuSystemTimeUs);
-                updatedUids.put(Process.SYSTEM_UID, updatedUids.get(Process.SYSTEM_UID, 0)
-                        + mTempTotalCpuUserTimeUs + mTempTotalCpuSystemTimeUs);
-
-                final Uid.Proc proc = u.getProcessStatsLocked("*lost*");
-                proc.addCpuTimeLocked((int) mTempTotalCpuUserTimeUs / 1000,
-                        (int) mTempTotalCpuSystemTimeUs / 1000);
+                timer.mInList = true;
+                mLastPartialTimers.add(timer);
             }
         }
+    }
 
+    /**
+     * Take snapshot of cpu times (aggregated over all uids) at different frequencies and
+     * calculate cpu times spent by each uid at different frequencies.
+     *
+     * @param updatedUids The uids for which times spent at different frequencies are calculated.
+     */
+    @VisibleForTesting
+    public void updateClusterSpeedTimes(@NonNull SparseLongArray updatedUids) {
         long totalCpuClustersTimeMs = 0;
         // Read the time spent for each cluster at various cpu frequencies.
         final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][];
@@ -10446,8 +10385,8 @@
                 final long appCpuTimeUs = updatedUids.valueAt(i);
                 // Add the cpu speeds to this UID.
                 final int numClusters = mPowerProfile.getNumCpuClusters();
-                if (u.mCpuClusterSpeedTimesUs == null || u.mCpuClusterSpeedTimesUs.length !=
-                        numClusters) {
+                if (u.mCpuClusterSpeedTimesUs == null ||
+                        u.mCpuClusterSpeedTimesUs.length != numClusters) {
                     u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
                 }
 
@@ -10471,66 +10410,155 @@
                 }
             }
         }
+    }
 
-        // See if there is a difference in wakelocks between this collection and the last
-        // collection.
-        if (ArrayUtils.referenceEquals(mPartialTimers, mLastPartialTimers)) {
-            // No difference, so each timer is now considered for the next collection.
-            for (int i = 0; i < numPartialTimers; i++) {
-                mPartialTimers.get(i).mInList = true;
-            }
-        } else {
-            // The lists are different, meaning we added (or removed a timer) since the last
-            // collection.
-            final int numLastPartialTimers = mLastPartialTimers.size();
-            for (int i = 0; i < numLastPartialTimers; i++) {
-                mLastPartialTimers.get(i).mInList = false;
-            }
-            mLastPartialTimers.clear();
+    /**
+     * Take a snapshot of the cpu times spent by each uid and update the corresponding counters.
+     * If {@param partialTimers} is not null and empty, then we assign a portion of cpu times to
+     * wakelock holders.
+     *
+     * @param partialTimers The wakelock holders among which the cpu times will be distributed.
+     * @param updatedUids If not null, then the uids found in the snapshot will be added to this.
+     */
+    @VisibleForTesting
+    public void readKernelUidCpuTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
+            @Nullable SparseLongArray updatedUids) {
+        mTempTotalCpuUserTimeUs = mTempTotalCpuSystemTimeUs = 0;
+        final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
+        final long startTimeMs = mClocks.uptimeMillis();
 
-            // Mark the current timers as gone through a collection.
-            for (int i = 0; i < numPartialTimers; i++) {
-                final StopwatchTimer timer = mPartialTimers.get(i);
-                timer.mInList = true;
-                mLastPartialTimers.add(timer);
+        mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+            uid = mapUid(uid);
+            if (Process.isIsolated(uid)) {
+                // This could happen if the isolated uid mapping was removed before that process
+                // was actually killed.
+                mKernelUidCpuTimeReader.removeUid(uid);
+                Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
+                return;
+            }
+            if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+                mKernelUidCpuTimeReader.removeUid(uid);
+                return;
+            }
+            final Uid u = getUidStatsLocked(uid);
+
+            // Accumulate the total system and user time.
+            mTempTotalCpuUserTimeUs += userTimeUs;
+            mTempTotalCpuSystemTimeUs += systemTimeUs;
+
+            StringBuilder sb = null;
+            if (DEBUG_ENERGY_CPU) {
+                sb = new StringBuilder();
+                sb.append("  got time for uid=").append(u.mUid).append(": u=");
+                TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                sb.append(" s=");
+                TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                sb.append("\n");
+            }
+
+            if (numWakelocks > 0) {
+                // We have wakelocks being held, so only give a portion of the
+                // time to the process. The rest will be distributed among wakelock
+                // holders.
+                userTimeUs = (userTimeUs * WAKE_LOCK_WEIGHT) / 100;
+                systemTimeUs = (systemTimeUs * WAKE_LOCK_WEIGHT) / 100;
+            }
+
+            if (sb != null) {
+                sb.append("  adding to uid=").append(u.mUid).append(": u=");
+                TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                sb.append(" s=");
+                TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                Slog.d(TAG, sb.toString());
+            }
+
+            u.mUserCpuTime.addCountLocked(userTimeUs);
+            u.mSystemCpuTime.addCountLocked(systemTimeUs);
+            if (updatedUids != null) {
+                updatedUids.put(u.getUid(), userTimeUs + systemTimeUs);
+            }
+        });
+
+        final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
+        if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
+            Slog.d(TAG, "Reading cpu stats took " + elapsedTimeMs + "ms");
+        }
+
+        if (numWakelocks > 0) {
+            // Distribute a portion of the total cpu time to wakelock holders.
+            mTempTotalCpuUserTimeUs = (mTempTotalCpuUserTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+            mTempTotalCpuSystemTimeUs =
+                    (mTempTotalCpuSystemTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+
+            for (int i = 0; i < numWakelocks; ++i) {
+                final StopwatchTimer timer = partialTimers.get(i);
+                final int userTimeUs = (int) (mTempTotalCpuUserTimeUs / (numWakelocks - i));
+                final int systemTimeUs = (int) (mTempTotalCpuSystemTimeUs / (numWakelocks - i));
+
+                if (DEBUG_ENERGY_CPU) {
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("  Distributing wakelock uid=").append(timer.mUid.mUid)
+                            .append(": u=");
+                    TimeUtils.formatDuration(userTimeUs / 1000, sb);
+                    sb.append(" s=");
+                    TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+                    Slog.d(TAG, sb.toString());
+                }
+
+                timer.mUid.mUserCpuTime.addCountLocked(userTimeUs);
+                timer.mUid.mSystemCpuTime.addCountLocked(systemTimeUs);
+                if (updatedUids != null) {
+                    final int uid = timer.mUid.getUid();
+                    updatedUids.put(uid, updatedUids.get(uid, 0) + userTimeUs + systemTimeUs);
+                }
+
+                final Uid.Proc proc = timer.mUid.getProcessStatsLocked("*wakelock*");
+                proc.addCpuTimeLocked(userTimeUs / 1000, systemTimeUs / 1000);
+
+                mTempTotalCpuUserTimeUs -= userTimeUs;
+                mTempTotalCpuSystemTimeUs -= systemTimeUs;
             }
         }
     }
 
-    void readKernelUidCpuFreqTimesLocked() {
-        mKernelUidCpuFreqTimeReader.readDelta(!mOnBatteryInternal ? null :
-                new KernelUidCpuFreqTimeReader.Callback() {
-                    @Override
-                    public void onCpuFreqs(long[] cpuFreqs) {
-                        mCpuFreqs = cpuFreqs;
-                    }
+    /**
+     * Take a snapshot of the cpu times spent by each uid in each freq and update the
+     * corresponding counters.
+     */
+    @VisibleForTesting
+    public void readKernelUidCpuFreqTimesLocked() {
+        mKernelUidCpuFreqTimeReader.readDelta(new KernelUidCpuFreqTimeReader.Callback() {
+            @Override
+            public void onCpuFreqs(long[] cpuFreqs) {
+                mCpuFreqs = cpuFreqs;
+            }
 
-                    @Override
-                    public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
-                        uid = mapUid(uid);
-                        if (Process.isIsolated(uid)) {
-                            mKernelUidCpuFreqTimeReader.removeUid(uid);
-                            Slog.d(TAG, "Got freq readings for an isolated uid with"
-                                    + " no mapping to owning uid: " + uid);
-                            return;
-                        }
-                        if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
-                            Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
-                            mKernelUidCpuFreqTimeReader.removeUid(uid);
-                            return;
-                        }
-                        final Uid u = getUidStatsLocked(uid);
-                        if (u.mCpuFreqTimeMs == null) {
-                            u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
-                        }
-                        u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
-                        if (u.mScreenOffCpuFreqTimeMs == null) {
-                            u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
-                                    mOnBatteryScreenOffTimeBase);
-                        }
-                        u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
-                    }
-                });
+            @Override
+            public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
+                uid = mapUid(uid);
+                if (Process.isIsolated(uid)) {
+                    mKernelUidCpuFreqTimeReader.removeUid(uid);
+                    Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
+                    return;
+                }
+                if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                    Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
+                    mKernelUidCpuFreqTimeReader.removeUid(uid);
+                    return;
+                }
+                final Uid u = getUidStatsLocked(uid);
+                if (u.mCpuFreqTimeMs == null) {
+                    u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+                }
+                u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+                if (u.mScreenOffCpuFreqTimeMs == null) {
+                    u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+                            mOnBatteryScreenOffTimeBase);
+                }
+                u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+            }
+        });
     }
 
     boolean setChargingLocked(boolean charging) {
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 5375651..a96b5dd 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -35,7 +35,8 @@
 import java.io.IOException;
 
 /**
- * Backup agent for various system-managed data, currently just the system wallpaper
+ * Backup agent for various system-managed data.  Wallpapers are now handled by a
+ * separate package, but we still process restores from legacy datasets here.
  */
 public class SystemBackupAgent extends BackupAgentHelper {
     private static final String TAG = "SystemBackupAgent";
@@ -61,16 +62,19 @@
     // TODO: http://b/22388012
     private static final String WALLPAPER_IMAGE_DIR =
             Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM).getAbsolutePath();
-    private static final String WALLPAPER_IMAGE = WallpaperBackupHelper.WALLPAPER_IMAGE;
+    public static final String WALLPAPER_IMAGE =
+            new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
+                    "wallpaper").getAbsolutePath();
 
     // TODO: Will need to change if backing up non-primary user's wallpaper
     // TODO: http://b/22388012
     private static final String WALLPAPER_INFO_DIR =
             Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM).getAbsolutePath();
-    private static final String WALLPAPER_INFO = WallpaperBackupHelper.WALLPAPER_INFO;
+    public static final String WALLPAPER_INFO =
+            new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
+                    "wallpaper_info.xml").getAbsolutePath();
     // Use old keys to keep legacy data compatibility and avoid writing two wallpapers
     private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
-    private static final String WALLPAPER_INFO_KEY = WallpaperBackupHelper.WALLPAPER_INFO_KEY;
 
     private WallpaperBackupHelper mWallpaperHelper = null;
 
@@ -98,13 +102,11 @@
         // Slot in a restore helper for the older wallpaper backup schema to support restore
         // from devices still generating data in that format.
         mWallpaperHelper = new WallpaperBackupHelper(this,
-                new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO },
-                new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} );
+                new String[] { WALLPAPER_IMAGE_KEY} );
         addHelper(WALLPAPER_HELPER, mWallpaperHelper);
 
         // On restore, we also support a long-ago wallpaper data schema "system_files"
         addHelper("system_files", new WallpaperBackupHelper(this,
-                new String[] { WALLPAPER_IMAGE },
                 new String[] { WALLPAPER_IMAGE_KEY} ));
 
         addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
@@ -115,27 +117,12 @@
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
 
-        try {
-            super.onRestore(data, appVersionCode, newState);
-
-            IWallpaperManager wallpaper = (IWallpaperManager) ServiceManager.getService(
-                    Context.WALLPAPER_SERVICE);
-            if (wallpaper != null) {
-                try {
-                    wallpaper.settingsRestored();
-                } catch (RemoteException re) {
-                    Slog.e(TAG, "Couldn't restore settings\n" + re);
-                }
-            }
-        } catch (IOException ex) {
-            // If there was a failure, delete everything for the wallpaper, this is too aggressive,
-            // but this is hopefully a rare failure.
-            Slog.d(TAG, "restore failed", ex);
-            (new File(WALLPAPER_IMAGE)).delete();
-            (new File(WALLPAPER_INFO)).delete();
-        }
+        super.onRestore(data, appVersionCode, newState);
     }
 
+    /**
+     * Support for 'adb restore' of legacy archives
+     */
     @Override
     public void onRestoreFile(ParcelFileDescriptor data, long size,
             int type, String domain, String path, long mode, long mtime)
@@ -183,12 +170,4 @@
             }
         }
     }
-
-    @Override
-    public void onRestoreFinished() {
-        // helper will be null following 'adb restore' or other full-data operation
-        if (mWallpaperHelper != null) {
-            mWallpaperHelper.onRestoreFinished();
-        }
-    }
 }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1ad8541..5afd067 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -814,6 +814,12 @@
             addOption("-Ximage-compiler-option");
             addOption("--compiled-classes=/system/etc/compiled-classes");
         }
+
+        // If there is a dirty-image-objects file, push it.
+        if (hasFile("/system/etc/dirty-image-objects")) {
+            addOption("-Ximage-compiler-option");
+            addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+        }
     }
 
     property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 7b381b4..bfb25113 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -44,7 +44,7 @@
     mWidth = width;
     mHeight = height;
     SkCanvas* canvas = mRecorder->beginRecording(SkIntToScalar(width), SkIntToScalar(height));
-    return Canvas::create_canvas(canvas);
+    return Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer);
 }
 
 void Picture::endRecording() {
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index e2dc52b..abc3599 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -21,6 +21,7 @@
 
 #include "CreateJavaOutputStreamAdaptor.h"
 
+#include "SkColorSpaceXformCanvas.h"
 #include "SkDocument.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
@@ -94,7 +95,10 @@
 
             SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight,
                     &(page->mContentRect));
-            canvas->drawPicture(page->mPicture);
+            std::unique_ptr<SkCanvas> toSRGBCanvas =
+                    SkCreateColorSpaceXformCanvas(canvas, SkColorSpace::MakeSRGB());
+
+            toSRGBCanvas->drawPicture(page->mPicture);
 
             document->endPage();
         }
@@ -127,7 +131,7 @@
     PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
     SkCanvas* canvas = document->startPage(pageWidth, pageHeight,
             contentLeft, contentTop, contentRight, contentBottom);
-    return reinterpret_cast<jlong>(Canvas::create_canvas(canvas));
+    return reinterpret_cast<jlong>(Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer));
 }
 
 static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 31e954b..c457ab0 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -233,8 +233,9 @@
     for (;;) {
         uint32_t seq;
         InputEvent* inputEvent;
+        int32_t displayId;
         status_t status = mInputConsumer.consume(&mInputEventFactory,
-                consumeBatches, frameTime, &seq, &inputEvent);
+                consumeBatches, frameTime, &seq, &inputEvent, &displayId);
         if (status) {
             if (status == WOULD_BLOCK) {
                 if (!skipCallbacks && !mBatchedInputEventPending
@@ -311,7 +312,8 @@
                     ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
                 }
                 env->CallVoidMethod(receiverObj.get(),
-                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
+                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
+                        displayId);
                 if (env->ExceptionCheck()) {
                     ALOGE("Exception dispatching input event.");
                     skipCallbacks = true;
@@ -417,7 +419,7 @@
 
     gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz,
-            "dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+            "dispatchInputEvent", "(ILandroid/view/InputEvent;I)V");
     gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
             gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
 
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 420ff2a..58ccef18 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -39,6 +39,8 @@
 
 // Log debug messages about the dispatch cycle.
 static const bool kDebugDispatchCycle = false;
+// Display id for default(primary) display.
+static const int32_t kDefaultDisplayId = 0;
 
 static struct {
     jclass clazz;
@@ -136,6 +138,7 @@
         publishedSeq = mNextPublishedSeq++;
         status_t status = mInputPublisher.publishMotionEvent(publishedSeq,
                 event->getDeviceId(), event->getSource(),
+                kDefaultDisplayId /* TODO(multi-display): propagate display id */,
                 event->getAction(), event->getActionButton(), event->getFlags(),
                 event->getEdgeFlags(), event->getMetaState(), event->getButtonState(),
                 event->getXOffset(), event->getYOffset(),
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3887c76..dba8194 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -108,13 +108,9 @@
      // changes its locking strategy or its use of syscalls within the
      // lazy-init critical section, its use here may become unsafe.
     if (WIFEXITED(status)) {
-      if (WEXITSTATUS(status)) {
-        ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
-      }
+      ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
     } else if (WIFSIGNALED(status)) {
-      if (WTERMSIG(status) != SIGKILL) {
-        ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
-      }
+      ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
       if (WCOREDUMP(status)) {
         ALOGI("Process %d dumped core.", pid);
       }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5914f56..76e683f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -31,6 +31,7 @@
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
+import "frameworks/base/core/proto/android/os/procrank.proto";
 
 package android.os;
 
@@ -53,7 +54,7 @@
     //SystemProperties system_properties = 1000;
 
     // Linux services
-    //Procrank procrank = 2000;
+    Procrank procrank = 2000;
     //PageTypeInfo page_type_info = 2001;
     KernelWakeSources kernel_wake_sources = 2002;
 
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 6ab1d29..e0b62aa 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -26,6 +26,7 @@
     repeated WakeupSourceProto wakeup_sources = 1;
 }
 
+// Next Tag: 11
 message WakeupSourceProto {
     // Name of the event which triggers application processor
     string name = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
new file mode 100644
index 0000000..c7dbf4d
--- /dev/null
+++ b/core/proto/android/os/procrank.proto
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 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 = "ProcrankProto";
+
+package android.os;
+
+//Memory usage of running processes
+message Procrank {
+    // Currently running process
+    repeated ProcessProto processes = 1;
+
+    // Summary
+    SummaryProto summary = 2;
+}
+
+// Next Tag: 11
+message ProcessProto {
+    // ID of the process
+    int32 pid = 1;
+
+    // virtual set size, unit KB
+    int64 vss = 2;
+
+    // resident set size, unit KB
+    int64 rss = 3;
+
+    // proportional set size, unit KB
+    int64 pss = 4;
+
+    // unique set size, unit KB
+    int64 uss = 5;
+
+    // swap size, unit KB
+    int64 swap = 6;
+
+    // proportional swap size, unit KB
+    int64 pswap = 7;
+
+    // unique swap size, unit KB
+    int64 uswap = 8;
+
+    // zswap size, unit KB
+    int64 zswap = 9;
+
+    // process command
+    string cmdline = 10;
+}
+
+// Next Tag: 3
+message SummaryProto {
+    ProcessProto total = 1;
+
+    ZramProto zram = 2;
+
+    RamProto ram = 3;
+}
+
+// TODO: sync on how to use these values
+message ZramProto {
+    string raw_text = 1;
+}
+
+message RamProto {
+    string raw_text = 1;
+}
diff --git a/core/res/res/drawable/toast_frame.xml b/core/res/res/drawable/toast_frame.xml
index 053b4f4..d57bd6a 100644
--- a/core/res/res/drawable/toast_frame.xml
+++ b/core/res/res/drawable/toast_frame.xml
@@ -17,8 +17,8 @@
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <!-- background is material_grey_300 with .9 alpha -->
-    <solid android:color="#E6E0E0E0" />
+    <!-- background is material_grey_200 with .9 alpha -->
+    <solid android:color="#E6EEEEEE" />
     <corners android:radius="22dp" />
 </shape>
 
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 2c08bf7..db586ec 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -34,8 +34,6 @@
         android:layout_gravity="center_horizontal"
         android:textAppearance="@style/TextAppearance.Toast"
         android:textColor="@color/primary_text_default_material_light"
-        android:shadowColor="#BB000000"
-        android:shadowRadius="2.75"
         />
 
 </LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4038010..4b64e3f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -525,6 +525,9 @@
     <!-- Integers specifying the max packet Tx/Rx rates for full scan -->
     <integer translatable="false" name="config_wifi_framework_max_tx_rate_for_full_scan">8</integer>
     <integer translatable="false" name="config_wifi_framework_max_rx_rate_for_full_scan">16</integer>
+    <!-- Integers specifying the min packet Tx/Rx rates in packets per second for staying on the same network -->
+    <integer translatable="false" name="config_wifi_framework_min_tx_rate_for_staying_on_network">16</integer>
+    <integer translatable="false" name="config_wifi_framework_min_rx_rate_for_staying_on_network">16</integer>
     <!-- Integer parameters of the wifi to cellular handover feature
          wifi should not stick to bad networks -->
     <integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-82</integer>
@@ -1388,6 +1391,7 @@
     <!-- The package name of the default network recommendation app.
          A network recommendation provider must:
              * Be granted the SCORE_NETWORKS permission.
+             * Be granted the ACCESS_COARSE_LOCATION permission.
              * Include a Service for the android.net.scoring.RECOMMEND_NETWORKS action
                protected by the BIND_NETWORK_RECOMMENDATION_SERVICE permission.
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index efd199e..d5a28f1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -377,6 +377,8 @@
   <java-symbol type="string"  name="config_wifi_framework_sap_2G_channel_list" />
   <java-symbol type="integer" name="config_wifi_framework_max_tx_rate_for_full_scan" />
   <java-symbol type="integer" name="config_wifi_framework_max_rx_rate_for_full_scan" />
+  <java-symbol type="integer" name="config_wifi_framework_min_tx_rate_for_staying_on_network" />
+  <java-symbol type="integer" name="config_wifi_framework_min_rx_rate_for_staying_on_network" />
 
   <java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
   <java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 6063e1a..0920d1e 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -33,6 +33,7 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
+import java.util.TimeZone;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -40,16 +41,20 @@
 
     private static final LocaleList LOCALE_LIST_US = new LocaleList(Locale.US);
     private LocaleList mOriginalLocales;
+    private TimeZone mOriginalTimeZone;
 
     @Before
     public void setup() {
         mOriginalLocales = Resources.getSystem().getConfiguration().getLocales();
         setLocales(LOCALE_LIST_US);
+        mOriginalTimeZone = TimeZone.getDefault();
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
     }
 
     @After
     public void teardown() {
         setLocales(mOriginalLocales);
+        TimeZone.setDefault(mOriginalTimeZone);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 29447ed..2a6c22e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -32,9 +32,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-import java.util.Locale;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
@@ -177,20 +174,6 @@
     }
 
     @Test
-    public void testLanguageDetection() {
-        if (isTextClassifierDisabled()) return;
-
-        String text = "This is a piece of English text";
-        assertThat(mTcm.detectLanguages(text), isDetectedLanguage("en"));
-
-        text = "Das ist ein deutscher Text";
-        assertThat(mTcm.detectLanguages(text), isDetectedLanguage("de"));
-
-        text = "これは日本語のテキストです";
-        assertThat(mTcm.detectLanguages(text), isDetectedLanguage("ja"));
-    }
-
-    @Test
     public void testSetTextClassifier() {
         TextClassifier classifier = mock(TextClassifier.class);
         mTcm.setTextClassifier(classifier);
@@ -270,30 +253,4 @@
             }
         };
     }
-
-    private static Matcher<List<TextLanguage>> isDetectedLanguage(final String language) {
-        return new BaseMatcher<List<TextLanguage>>() {
-            @Override
-            public boolean matches(Object o) {
-                if (o instanceof List) {
-                    List languages = (List) o;
-                    if (!languages.isEmpty()) {
-                        Object o1 = languages.get(0);
-                        if (o1 instanceof TextLanguage) {
-                            TextLanguage lang = (TextLanguage) o1;
-                            return lang.getLanguageCount() > 0
-                                    && new Locale(language).getLanguage()
-                                            .equals(lang.getLanguage(0).getLanguage());
-                        }
-                    }
-                }
-                return false;
-            }
-
-            @Override
-            public void describeTo(Description description) {
-                description.appendValue(String.format("%s", language));
-            }
-        };
-    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
new file mode 100644
index 0000000..e69cd5d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -0,0 +1,774 @@
+/*
+ * 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.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.FIRST_ISOLATED_UID;
+
+import static com.android.internal.os.BatteryStatsImpl.WAKE_LOCK_WEIGHT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseLongArray;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * To run the tests, use
+ *
+ * runtest -c com.android.internal.os.BatteryStatsCpuTimesTest frameworks-core
+ *
+ * or
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ *     ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsCpuTimesTest -w \
+ *     com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ *
+ * or
+ *
+ * bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsCpuTimesTest {
+    @Mock KernelUidCpuTimeReader mKernelUidCpuTimeReader;
+    @Mock KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+    @Mock BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
+    @Mock PowerProfile mPowerProfile;
+
+    private MockClocks mClocks;
+    private MockBatteryStatsImpl mBatteryStatsImpl;
+    private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mClocks = new MockClocks();
+        mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
+                .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader)
+                .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+                .setUserInfoProvider(mUserInfoProvider);
+    }
+
+    @Test
+    public void testUpdateCpuTimeLocked() {
+        // PRECONDITIONS
+        mBatteryStatsImpl.setPowerProfile(mPowerProfile);
+        mBatteryStatsImpl.setOnBatteryInternal(false);
+        final int numClusters = 3;
+        initKernelCpuSpeedReaders(numClusters);
+
+        // RUN
+        mBatteryStatsImpl.updateCpuTimeLocked(true);
+
+        // VERIFY
+        verify(mKernelUidCpuTimeReader).readDelta(null);
+        verify(mKernelUidCpuFreqTimeReader).readDelta(null);
+        for (int i = 0; i < numClusters; ++i) {
+            verify(mKernelCpuSpeedReaders[i]).readDelta();
+        }
+
+        // Prepare for next test
+        Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+        for (int i = 0; i < numClusters; ++i) {
+            Mockito.reset(mKernelCpuSpeedReaders[i]);
+        }
+
+        // PRECONDITIONS
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+
+        // RUN
+        mBatteryStatsImpl.updateCpuTimeLocked(true);
+
+        // VERIFY
+        verify(mUserInfoProvider).refreshUserIds();
+        verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+        verify(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+        for (int i = 0; i < numClusters; ++i) {
+            verify(mKernelCpuSpeedReaders[i]).readDelta();
+        }
+    }
+
+    @Test
+    public void testMarkPartialTimersAsEligible() {
+        // PRECONDITIONS
+        final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers = getPartialTimers(
+                10032, 10042, 10052);
+        final ArrayList<BatteryStatsImpl.StopwatchTimer> lastPartialTimers
+                = new ArrayList<>(partialTimers);
+        mBatteryStatsImpl.setPartialTimers(partialTimers);
+        mBatteryStatsImpl.setLastPartialTimers(lastPartialTimers);
+        final boolean[] inList = {false, true, false};
+        for (int i = 0; i < partialTimers.size(); ++i) {
+            partialTimers.get(i).mInList = inList[i];
+        }
+
+        // RUN
+        mBatteryStatsImpl.markPartialTimersAsEligible();
+
+        // VERIFY
+        assertTrue(ArrayUtils.referenceEquals(partialTimers, lastPartialTimers));
+        for (int i = 0; i < partialTimers.size(); ++i) {
+            assertTrue("Timer id=" + i, partialTimers.get(i).mInList);
+        }
+
+        // PRECONDITIONS
+        partialTimers.addAll(getPartialTimers(10077, 10099));
+        partialTimers.remove(1 /* index */);
+
+        // RUN
+        mBatteryStatsImpl.markPartialTimersAsEligible();
+
+        // VERIFY
+        assertTrue(ArrayUtils.referenceEquals(partialTimers, lastPartialTimers));
+        for (int i = 0; i < partialTimers.size(); ++i) {
+            assertTrue("Timer id=" + i, partialTimers.get(i).mInList);
+        }
+    }
+
+    @Test
+    public void testUpdateClusterSpeedTimes() {
+        // PRECONDITIONS
+
+        // Call updateTimeBasesLocked before setting the power profile to avoid execution of
+        // BatteryStatsImpl.updateCpuTimeLocked.
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+        mBatteryStatsImpl.setPowerProfile(mPowerProfile);
+        final long[][] clusterSpeedTimesMs = {{20, 30}, {40, 50, 60}};
+        initKernelCpuSpeedReaders(clusterSpeedTimesMs.length);
+        for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+            when(mKernelCpuSpeedReaders[i].readDelta()).thenReturn(clusterSpeedTimesMs[i]);
+        }
+        when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterSpeedTimesMs.length);
+        for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+            when(mPowerProfile.getNumSpeedStepsInCpuCluster(i))
+                    .thenReturn(clusterSpeedTimesMs[i].length);
+        }
+        final SparseLongArray updatedUids = new SparseLongArray();
+        final int[] testUids = {10012, 10014, 10016};
+        final int[] cpuTimeUs = {89, 31, 43};
+        for (int i = 0; i < testUids.length; ++i) {
+            updatedUids.put(testUids[i], cpuTimeUs[i]);
+        }
+
+        // RUN
+        mBatteryStatsImpl.updateClusterSpeedTimes(updatedUids);
+
+        // VERIFY
+        int totalClustersTimeMs = 0;
+        for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+            for (int j = 0; j < clusterSpeedTimesMs[i].length; ++j) {
+                totalClustersTimeMs += clusterSpeedTimesMs[i][j];
+            }
+        }
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            for (int cluster = 0; cluster < clusterSpeedTimesMs.length; ++cluster) {
+                for (int speed = 0; speed < clusterSpeedTimesMs[cluster].length; ++speed) {
+                    assertEquals("Uid=" + testUids[i] + ", cluster=" + cluster + ", speed=" + speed,
+                            cpuTimeUs[i] * clusterSpeedTimesMs[cluster][speed]
+                                    / totalClustersTimeMs,
+                            u.getTimeAtCpuSpeed(cluster, speed, STATS_SINCE_CHARGED));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testReadKernelUidCpuTimesLocked() {
+        //PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+        final int testUserId = 11;
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                FIRST_APPLICATION_UID + 27,
+                FIRST_APPLICATION_UID + 33
+        });
+        final long[][] uidTimesUs = {
+                {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+            }
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        final SparseLongArray updatedUids = new SparseLongArray();
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, updatedUids);
+
+        // VERIFY
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+
+            assertEquals("Unexpected entry in updated uids for uid=" + testUids[i],
+                    uidTimesUs[i][0] + uidTimesUs[i][1], updatedUids.get(testUids[i]));
+            updatedUids.delete(testUids[i]);
+        }
+        assertEquals("Updated uids: " + updatedUids, 0, updatedUids.size());
+
+        // Repeat the test with a null updatedUids
+
+        // PRECONDITIONS
+        final long[][] deltasUs = {
+                {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+            }
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+        // VERIFY
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][0] + deltasUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][1] + deltasUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+        }
+    }
+
+    @Test
+    public void testReadKernelUidCpuTimesLocked_isolatedUid() {
+        //PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+        final int testUserId = 11;
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        final int isolatedAppId = FIRST_ISOLATED_UID + 27;
+        final int isolatedUid = UserHandle.getUid(testUserId, isolatedAppId);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                isolatedAppId,
+                FIRST_APPLICATION_UID + 33
+        });
+        final long[][] uidTimesUs = {
+                {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+            }
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+        // VERIFY
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            if (UserHandle.isIsolated(testUids[i])) {
+                assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+                continue;
+            }
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+        }
+        verify(mKernelUidCpuTimeReader).removeUid(isolatedUid);
+
+        // Add an isolated uid mapping and repeat the test.
+
+        // PRECONDITIONS
+        final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
+        mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+        final long[][] deltasUs = {
+                {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+            }
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+        // VERIFY
+        for (int i = 0; i < testUids.length; ++i) {
+            BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            final long expectedUserTimeUs;
+            final long expectedSystemTimeUs;
+            if (UserHandle.isIsolated(testUids[i])) {
+                assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+                // Since we added a mapping, an entry should've been created for owner uid.
+                u = mBatteryStatsImpl.getUidStats().get(ownerUid);
+                expectedUserTimeUs = deltasUs[i][0];
+                expectedSystemTimeUs = deltasUs[i][1];
+                assertNotNull("No entry for owner uid=" + ownerUid, u);
+            } else {
+                assertNotNull("No entry for uid=" + testUids[i], u);
+                expectedUserTimeUs = uidTimesUs[i][0] + deltasUs[i][0];
+                expectedSystemTimeUs = uidTimesUs[i][1] + deltasUs[i][1];
+            }
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+        }
+    }
+
+    @Test
+    public void testReadKernelUidCpuTimesLocked_invalidUid() {
+        //PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+        final int testUserId = 11;
+        final int invalidUserId = 15;
+        final int invalidUid = UserHandle.getUid(invalidUserId, FIRST_APPLICATION_UID + 99);
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        when(mUserInfoProvider.exists(invalidUserId)).thenReturn(false);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                FIRST_APPLICATION_UID + 27,
+                FIRST_APPLICATION_UID + 33
+        });
+        final long[][] uidTimesUs = {
+                {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+            }
+            // And one for the invalid uid
+            callback.onUidCpuTime(invalidUid, 3879, 239);
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+        // VERIFY
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+        }
+        assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
+                mBatteryStatsImpl.getUidStats().get(invalidUid));
+        verify(mKernelUidCpuTimeReader).removeUid(invalidUid);
+    }
+
+    @Test
+    public void testReadKernelUidCpuTimesLocked_withPartialTimers() {
+        //PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+        final int testUserId = 11;
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                FIRST_APPLICATION_UID + 27,
+                FIRST_APPLICATION_UID + 33
+        });
+        final int[] partialTimerUids = {
+                UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 48),
+                UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 10)
+        };
+        final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers
+                = getPartialTimers(partialTimerUids);
+        final long[][] uidTimesUs = {
+                {12, 34}, {3394, 3123}, {7977, 80434}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuTimeReader.Callback callback =
+                    (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+            }
+            return null;
+        }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+        // RUN
+        final SparseLongArray updatedUids = new SparseLongArray();
+        mBatteryStatsImpl.readKernelUidCpuTimesLocked(partialTimers, updatedUids);
+
+        // VERIFY
+        long totalUserTimeUs = 0;
+        long totalSystemTimeUs = 0;
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+            final long expectedUserTimeUs = uidTimesUs[i][0] * WAKE_LOCK_WEIGHT / 100;
+            final long expectedSystemTimeUs = uidTimesUs[i][1] * WAKE_LOCK_WEIGHT / 100;
+            assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+                    expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+                    expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected entry in updated uids for uid=" + testUids[i],
+                    expectedUserTimeUs + expectedSystemTimeUs, updatedUids.get(testUids[i]));
+            updatedUids.delete(testUids[i]);
+            totalUserTimeUs += uidTimesUs[i][0];
+            totalSystemTimeUs += uidTimesUs[i][1];
+        }
+
+        totalUserTimeUs = totalUserTimeUs * (100 - WAKE_LOCK_WEIGHT) / 100;
+        totalSystemTimeUs = totalSystemTimeUs * (100 - WAKE_LOCK_WEIGHT) / 100;
+        for (int i = 0; i < partialTimerUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(partialTimerUids[i]);
+            assertNotNull("No entry for partial timer uid=" + partialTimerUids[i], u);
+            final long expectedUserTimeUs = totalUserTimeUs / (partialTimerUids.length - i);
+            final long expectedSystemTimeUs = totalSystemTimeUs / (partialTimerUids.length - i);
+            assertEquals("Unexpected user cpu time for partial timer uid=" + partialTimerUids[i],
+                    expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for partial timer uid=" + partialTimerUids[i],
+                    expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected entry in updated uids for partial timer uid="
+                            + partialTimerUids[i],
+                    expectedUserTimeUs + expectedSystemTimeUs,
+                    updatedUids.get(partialTimerUids[i]));
+            updatedUids.delete(partialTimerUids[i]);
+            totalUserTimeUs -= expectedUserTimeUs;
+            totalSystemTimeUs -= expectedSystemTimeUs;
+
+            final BatteryStats.Uid.Proc proc = u.getProcessStats().get("*wakelock*");
+            assertEquals("Unexpected user cpu time for *wakelock* in uid=" + partialTimerUids[i],
+                    expectedUserTimeUs / 1000, proc.getUserTime(STATS_SINCE_CHARGED));
+            assertEquals("Unexpected system cpu time for *wakelock* in uid=" + partialTimerUids[i],
+                    expectedSystemTimeUs / 1000, proc.getSystemTime(STATS_SINCE_CHARGED));
+        }
+        assertEquals(0, totalUserTimeUs);
+        assertEquals(0, totalSystemTimeUs);
+        assertEquals("Updated uids: " + updatedUids, 0, updatedUids.size());
+    }
+
+    @Test
+    public void testReadKernelUidCpuFreqTimesLocked() {
+        // PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+        final int testUserId = 11;
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        final int[] testUids = getUids(testUserId, new int[] {
+            FIRST_APPLICATION_UID + 22,
+            FIRST_APPLICATION_UID + 27,
+            FIRST_APPLICATION_UID + 33
+        });
+        final long[] freqs = {1, 12, 123, 12, 1234};
+        final long[][] uidTimesMs = {
+                {4, 10, 5, 9, 4},
+                {5, 1, 12, 2, 10},
+                {8, 25, 3, 0, 42}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuFreqTimeReader.Callback callback =
+                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            callback.onCpuFreqs(freqs);
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+            }
+            return null;
+        }).when(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+        // VERIFY
+        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+
+            assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+                    uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+            assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+                    u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+        }
+
+        // Repeat the test when the screen is off.
+
+        // PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, true, 0, 0);
+        final long[][] deltasMs = {
+                {3, 12, 55, 100, 32},
+                {3248327490475l, 232349349845043l, 123, 2398, 0},
+                {43, 3345, 2143, 123, 4554}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuFreqTimeReader.Callback callback =
+                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            callback.onCpuFreqs(freqs);
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+            }
+            return null;
+        }).when(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+        // VERIFY
+        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+
+            assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+                    sum(uidTimesMs[i], deltasMs[i]), u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+            assertArrayEquals("Unexpected screen-off cpu times for uid=" + testUids[i],
+                    deltasMs[i], u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+        }
+    }
+
+    @Test
+    public void testReadKernelUidCpuFreqTimesLocked_isolatedUid() {
+        // PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+        final int testUserId = 11;
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        final int isolatedAppId = FIRST_ISOLATED_UID + 27;
+        final int isolatedUid = UserHandle.getUid(testUserId, isolatedAppId);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                isolatedAppId,
+                FIRST_APPLICATION_UID + 33
+        });
+        final long[] freqs = {1, 12, 123, 12, 1234};
+        final long[][] uidTimesMs = {
+                {4, 10, 5, 9, 4},
+                {5, 1, 12, 2, 10},
+                {8, 25, 3, 0, 42}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuFreqTimeReader.Callback callback =
+                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            callback.onCpuFreqs(freqs);
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+            }
+            return null;
+        }).when(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+        // VERIFY
+        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            if (UserHandle.isIsolated(testUids[i])) {
+                assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+                continue;
+            }
+            assertNotNull("No entry for uid=" + testUids[i], u);
+
+            assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+                    uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+            assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+                    u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+        }
+        verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid);
+
+
+        // Add an isolated uid mapping and repeat the test.
+
+        // PRECONDITIONS
+        final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
+        mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+        final long[][] deltasMs = {
+                {3, 12, 55, 100, 32},
+                {32483274, 232349349, 123, 2398, 0},
+                {43, 3345, 2143, 123, 4554}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuFreqTimeReader.Callback callback =
+                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            callback.onCpuFreqs(freqs);
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+            }
+            return null;
+        }).when(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+        // VERIFY
+        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+        for (int i = 0; i < testUids.length; ++i) {
+            BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            final long[] expectedTimes;
+            if (UserHandle.isIsolated(testUids[i])) {
+                assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+                // Since we added a mapping, an entry should've been created for owner uid.
+                u = mBatteryStatsImpl.getUidStats().get(ownerUid);
+                expectedTimes = deltasMs[i];
+                assertNotNull("No entry for owner uid=" + ownerUid, u);
+            } else {
+                assertNotNull("No entry for uid=" + testUids[i], u);
+                expectedTimes = sum(uidTimesMs[i], deltasMs[i]);
+            }
+
+            assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+                    expectedTimes, u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+            assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+                    u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+        }
+    }
+
+    @Test
+    public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+        // PRECONDITIONS
+        mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+        final int testUserId = 11;
+        final int invalidUserId = 15;
+        final int invalidUid = UserHandle.getUid(invalidUserId, FIRST_APPLICATION_UID + 99);
+        when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+        when(mUserInfoProvider.exists(invalidUserId)).thenReturn(false);
+        final int[] testUids = getUids(testUserId, new int[] {
+                FIRST_APPLICATION_UID + 22,
+                FIRST_APPLICATION_UID + 27,
+                FIRST_APPLICATION_UID + 33
+        });
+        final long[] freqs = {1, 12, 123, 12, 1234};
+        final long[][] uidTimesMs = {
+                {4, 10, 5, 9, 4},
+                {5, 1, 12, 2, 10},
+                {8, 25, 3, 0, 42}
+        };
+        doAnswer(invocation -> {
+            final KernelUidCpuFreqTimeReader.Callback callback =
+                    (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+            callback.onCpuFreqs(freqs);
+            for (int i = 0; i < testUids.length; ++i) {
+                callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+            }
+            // And one for the invalid uid
+            callback.onUidCpuFreqTime(invalidUid, new long[] {12, 839, 32, 34, 21});
+            return null;
+        }).when(mKernelUidCpuFreqTimeReader).readDelta(
+                any(KernelUidCpuFreqTimeReader.Callback.class));
+
+        // RUN
+        mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+        // VERIFY
+        assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            assertNotNull("No entry for uid=" + testUids[i], u);
+
+            assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+                    uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+            assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+                    u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+        }
+        assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
+                mBatteryStatsImpl.getUidStats().get(invalidUid));
+        verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
+    }
+
+    private void initKernelCpuSpeedReaders(int count) {
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[count];
+        for (int i = 0; i < count; ++i) {
+            mKernelCpuSpeedReaders[i] = Mockito.mock(KernelCpuSpeedReader.class);
+        }
+        mBatteryStatsImpl.setKernelCpuSpeedReaders(mKernelCpuSpeedReaders);
+    }
+
+    private ArrayList<BatteryStatsImpl.StopwatchTimer> getPartialTimers(int... uids) {
+        final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers = new ArrayList<>();
+        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
+        for (int uid : uids) {
+            final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uid);
+            final BatteryStatsImpl.StopwatchTimer timer = new BatteryStatsImpl.StopwatchTimer(
+                    mClocks, u, WAKE_TYPE_PARTIAL, null, timeBase);
+            partialTimers.add(timer);
+        }
+        return partialTimers;
+    }
+
+    private long[] sum(long[] a, long[] b) {
+        assertEquals("Arrays a: " + Arrays.toString(a) + ", b: " + Arrays.toString(b),
+                a.length, b.length);
+        final long[] result = new long[a.length];
+        for (int i = 0; i < a.length; ++i) {
+            result[i] = a[i] + b[i];
+        }
+        return result;
+    }
+
+    private int[] getUids(int userId, int[] appIds) {
+        final int[] uids = new int[appIds.length];
+        for (int i = appIds.length - 1; i >= 0; --i) {
+            uids[i] = UserHandle.getUid(userId, appIds[i]);
+        }
+        return uids;
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index f171afc..c1ca864 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -20,6 +20,7 @@
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+        BatteryStatsCpuTimesTest.class,
         BatteryStatsBackgroundStatsTest.class,
         BatteryStatsCounterTest.class,
         BatteryStatsDualTimerTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index a123fce..73ee3e5 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import java.util.ArrayList;
+
 /**
  * Mocks a BatteryStatsImpl object.
  */
@@ -48,5 +50,45 @@
     public TimeBase getOnBatteryScreenOffBackgroundTimeBase(int uid) {
         return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
     }
+
+    public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) {
+        mPowerProfile = powerProfile;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) {
+        mKernelUidCpuFreqTimeReader = reader;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) {
+        mKernelUidCpuTimeReader = reader;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
+        mKernelCpuSpeedReaders = readers;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setUserInfoProvider(UserInfoProvider provider) {
+        mUserInfoProvider = provider;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setPartialTimers(ArrayList<StopwatchTimer> partialTimers) {
+        mPartialTimers = partialTimers;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setLastPartialTimers(ArrayList<StopwatchTimer> lastPartialTimers) {
+        mLastPartialTimers = lastPartialTimers;
+        return this;
+    }
+
+    public MockBatteryStatsImpl setOnBatteryInternal(boolean onBatteryInternal) {
+        mOnBatteryInternal = onBatteryInternal;
+        return this;
+    }
 }
 
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 2a10bdd..b3bba07 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -15,7 +15,7 @@
 #
 # Generic key layout file for full alphabetic US English PC style external keyboards.
 #
-# This file is intentionally very generic and is intended to support a broad rang of keyboards.
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
 # Do not edit the generic key layout to support a specific keyboard; instead, create
 # a new key layout file with the required keyboard configuration.
 #
diff --git a/data/keyboards/Generic_Iot.kl b/data/keyboards/Generic_Iot.kl
new file mode 100644
index 0000000..89df224
--- /dev/null
+++ b/data/keyboards/Generic_Iot.kl
@@ -0,0 +1,443 @@
+# 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.
+
+#
+# Generic key layout file for full alphabetic US English PC style external keyboards.
+#
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
+# Do not edit the generic key layout to support a specific keyboard; instead, create
+# a new key layout file with the required keyboard configuration.
+#
+
+key 1     ESCAPE
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 12    MINUS
+key 13    EQUALS
+key 14    DEL
+key 15    TAB
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 26    LEFT_BRACKET
+key 27    RIGHT_BRACKET
+key 28    ENTER
+key 29    CTRL_LEFT
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+key 41    GRAVE
+key 42    SHIFT_LEFT
+key 43    BACKSLASH
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 54    SHIFT_RIGHT
+key 55    NUMPAD_MULTIPLY
+key 56    ALT_LEFT
+key 57    SPACE
+key 58    CAPS_LOCK
+key 59    F1
+key 60    F2
+key 61    F3
+key 62    F4
+key 63    F5
+key 64    F6
+key 65    F7
+key 66    F8
+key 67    F9
+key 68    F10
+key 69    NUM_LOCK
+key 70    SCROLL_LOCK
+key 71    NUMPAD_7
+key 72    NUMPAD_8
+key 73    NUMPAD_9
+key 74    NUMPAD_SUBTRACT
+key 75    NUMPAD_4
+key 76    NUMPAD_5
+key 77    NUMPAD_6
+key 78    NUMPAD_ADD
+key 79    NUMPAD_1
+key 80    NUMPAD_2
+key 81    NUMPAD_3
+key 82    NUMPAD_0
+key 83    NUMPAD_DOT
+# key 84 (undefined)
+key 85    ZENKAKU_HANKAKU
+key 86    BACKSLASH
+key 87    F11
+key 88    F12
+key 89    RO
+# key 90 "KEY_KATAKANA"
+# key 91 "KEY_HIRAGANA"
+key 92    HENKAN
+key 93    KATAKANA_HIRAGANA
+key 94    MUHENKAN
+key 95    NUMPAD_COMMA
+key 96    NUMPAD_ENTER
+key 97    CTRL_RIGHT
+key 98    NUMPAD_DIVIDE
+key 99    SYSRQ
+key 100   ALT_RIGHT
+# key 101 "KEY_LINEFEED"
+key 102   MOVE_HOME
+key 103   DPAD_UP
+key 104   PAGE_UP
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 107   MOVE_END
+key 108   DPAD_DOWN
+key 109   PAGE_DOWN
+key 110   INSERT
+key 111   FORWARD_DEL
+# key 112 "KEY_MACRO"
+key 113   VOLUME_MUTE
+key 114   VOLUME_DOWN
+key 115   VOLUME_UP
+key 116   POWER
+key 117   NUMPAD_EQUALS
+# key 118 "KEY_KPPLUSMINUS"
+key 119   BREAK
+# key 120 (undefined)
+key 121   NUMPAD_COMMA
+key 122   KANA
+key 123   EISU
+key 124   YEN
+key 125   META_LEFT
+key 126   META_RIGHT
+key 127   MENU
+key 128   MEDIA_STOP
+# key 129 "KEY_AGAIN"
+# key 130 "KEY_PROPS"
+# key 131 "KEY_UNDO"
+# key 132 "KEY_FRONT"
+key 133   COPY
+# key 134 "KEY_OPEN"
+key 135   PASTE
+# key 136 "KEY_FIND"
+key 137   CUT
+# key 138 "KEY_HELP"
+key 139   MENU
+key 140   CALCULATOR
+# key 141 "KEY_SETUP"
+key 142   SLEEP
+key 143   WAKEUP
+# key 144 "KEY_FILE"
+# key 145 "KEY_SENDFILE"
+# key 146 "KEY_DELETEFILE"
+# key 147 "KEY_XFER"
+# key 148 "KEY_PROG1"
+# key 149 "KEY_PROG2"
+key 150   EXPLORER
+# key 151 "KEY_MSDOS"
+key 152   POWER
+# key 153 "KEY_DIRECTION"
+# key 154 "KEY_CYCLEWINDOWS"
+key 155   ENVELOPE
+key 156   BOOKMARK
+# key 157 "KEY_COMPUTER"
+key 158   BACK
+key 159   FORWARD
+key 160   MEDIA_CLOSE
+key 161   MEDIA_EJECT
+key 162   MEDIA_EJECT
+key 163   MEDIA_NEXT
+key 164   MEDIA_PLAY_PAUSE
+key 165   MEDIA_PREVIOUS
+key 166   MEDIA_STOP
+key 167   MEDIA_RECORD
+key 168   MEDIA_REWIND
+key 169   CALL
+# key 170 "KEY_ISO"
+key 171   MUSIC
+key 172   HOME
+# key 173 "KEY_REFRESH"
+# key 174 "KEY_EXIT"
+# key 175 "KEY_MOVE"
+# key 176 "KEY_EDIT"
+key 177   PAGE_UP
+key 178   PAGE_DOWN
+key 179   NUMPAD_LEFT_PAREN
+key 180   NUMPAD_RIGHT_PAREN
+# key 181 "KEY_NEW"
+# key 182 "KEY_REDO"
+# key 183   F13
+# key 184   F14
+# key 185   F15
+# key 186   F16
+# key 187   F17
+# key 188   F18
+# key 189   F19
+# key 190   F20
+# key 191   F21
+# key 192   F22
+# key 193   F23
+# key 194   F24
+# key 195 (undefined)
+# key 196 (undefined)
+# key 197 (undefined)
+# key 198 (undefined)
+# key 199 (undefined)
+key 200   MEDIA_PLAY
+key 201   MEDIA_PAUSE
+# key 202 "KEY_PROG3"
+# key 203 "KEY_PROG4"
+# key 204 (undefined)
+# key 205 "KEY_SUSPEND"
+# key 206 "KEY_CLOSE"
+key 207   MEDIA_PLAY
+key 208   MEDIA_FAST_FORWARD
+# key 209 "KEY_BASSBOOST"
+# key 210 "KEY_PRINT"
+# key 211 "KEY_HP"
+key 212   CAMERA
+key 213   MUSIC
+# key 214 "KEY_QUESTION"
+key 215   ENVELOPE
+# key 216 "KEY_CHAT"
+key 217   SEARCH
+# key 218 "KEY_CONNECT"
+# key 219 "KEY_FINANCE"
+# key 220 "KEY_SPORT"
+# key 221 "KEY_SHOP"
+# key 222 "KEY_ALTERASE"
+# key 223 "KEY_CANCEL"
+key 224   BRIGHTNESS_DOWN
+key 225   BRIGHTNESS_UP
+key 226   HEADSETHOOK
+key 227   POUND
+key 228   STAR
+
+key 256   BUTTON_1
+key 257   BUTTON_2
+key 258   BUTTON_3
+key 259   BUTTON_4
+key 260   BUTTON_5
+key 261   BUTTON_6
+key 262   BUTTON_7
+key 263   BUTTON_8
+key 264   BUTTON_9
+key 265   BUTTON_10
+key 266   BUTTON_11
+key 267   BUTTON_12
+key 268   BUTTON_13
+key 269   BUTTON_14
+key 270   BUTTON_15
+key 271   BUTTON_16
+
+key 288   BUTTON_1
+key 289   BUTTON_2
+key 290   BUTTON_3
+key 291   BUTTON_4
+key 292   BUTTON_5
+key 293   BUTTON_6
+key 294   BUTTON_7
+key 295   BUTTON_8
+key 296   BUTTON_9
+key 297   BUTTON_10
+key 298   BUTTON_11
+key 299   BUTTON_12
+key 300   BUTTON_13
+key 301   BUTTON_14
+key 302   BUTTON_15
+key 303   BUTTON_16
+
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 306   BUTTON_C
+key 307   BUTTON_X
+key 308   BUTTON_Y
+key 309   BUTTON_Z
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+key 312   BUTTON_L2
+key 313   BUTTON_R2
+key 314   BUTTON_SELECT
+key 315   BUTTON_START
+key 316   BUTTON_MODE
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+
+# key 352 "KEY_OK"
+key 353   DPAD_CENTER
+# key 354 "KEY_GOTO"
+# key 355 "KEY_CLEAR"
+# key 356 "KEY_POWER2"
+# key 357 "KEY_OPTION"
+# key 358 "KEY_INFO"
+# key 359 "KEY_TIME"
+# key 360 "KEY_VENDOR"
+# key 361 "KEY_ARCHIVE"
+key 362   GUIDE
+# key 363 "KEY_CHANNEL"
+# key 364 "KEY_FAVORITES"
+# key 365 "KEY_EPG"
+key 366   DVR
+# key 367 "KEY_MHP"
+# key 368 "KEY_LANGUAGE"
+# key 369 "KEY_TITLE"
+# key 370 "KEY_SUBTITLE"
+# key 371 "KEY_ANGLE"
+# key 372 "KEY_ZOOM"
+# key 373 "KEY_MODE"
+# key 374 "KEY_KEYBOARD"
+# key 375 "KEY_SCREEN"
+# key 376 "KEY_PC"
+key 377   TV
+# key 378 "KEY_TV2"
+# key 379 "KEY_VCR"
+# key 380 "KEY_VCR2"
+# key 381 "KEY_SAT"
+# key 382 "KEY_SAT2"
+# key 383 "KEY_CD"
+# key 384 "KEY_TAPE"
+# key 385 "KEY_RADIO"
+# key 386 "KEY_TUNER"
+# key 387 "KEY_PLAYER"
+# key 388 "KEY_TEXT"
+# key 389 "KEY_DVD"
+# key 390 "KEY_AUX"
+# key 391 "KEY_MP3"
+# key 392 "KEY_AUDIO"
+# key 393 "KEY_VIDEO"
+# key 394 "KEY_DIRECTORY"
+# key 395 "KEY_LIST"
+# key 396 "KEY_MEMO"
+key 397   CALENDAR
+# key 398 "KEY_RED"
+# key 399 "KEY_GREEN"
+# key 400 "KEY_YELLOW"
+# key 401 "KEY_BLUE"
+key 402   CHANNEL_UP
+key 403   CHANNEL_DOWN
+# key 404 "KEY_FIRST"
+# key 405 "KEY_LAST"
+# key 406 "KEY_AB"
+# key 407 "KEY_NEXT"
+# key 408 "KEY_RESTART"
+# key 409 "KEY_SLOW"
+# key 410 "KEY_SHUFFLE"
+# key 411 "KEY_BREAK"
+# key 412 "KEY_PREVIOUS"
+# key 413 "KEY_DIGITS"
+# key 414 "KEY_TEEN"
+# key 415 "KEY_TWEN"
+
+key 429   CONTACTS
+
+# key 448 "KEY_DEL_EOL"
+# key 449 "KEY_DEL_EOS"
+# key 450 "KEY_INS_LINE"
+# key 451 "KEY_DEL_LINE"
+
+
+key 464   FUNCTION
+key 465   ESCAPE            FUNCTION
+key 466   F1                FUNCTION
+key 467   F2                FUNCTION
+key 468   F3                FUNCTION
+key 469   F4                FUNCTION
+key 470   F5                FUNCTION
+key 471   F6                FUNCTION
+key 472   F7                FUNCTION
+key 473   F8                FUNCTION
+key 474   F9                FUNCTION
+key 475   F10               FUNCTION
+key 476   F11               FUNCTION
+key 477   F12               FUNCTION
+key 478   1                 FUNCTION
+key 479   2                 FUNCTION
+key 480   D                 FUNCTION
+key 481   E                 FUNCTION
+key 482   F                 FUNCTION
+key 483   S                 FUNCTION
+key 484   B                 FUNCTION
+
+
+# key 497 KEY_BRL_DOT1
+# key 498 KEY_BRL_DOT2
+# key 499 KEY_BRL_DOT3
+# key 500 KEY_BRL_DOT4
+# key 501 KEY_BRL_DOT5
+# key 502 KEY_BRL_DOT6
+# key 503 KEY_BRL_DOT7
+# key 504 KEY_BRL_DOT8
+
+key 580   APP_SWITCH
+key 582   VOICE_ASSIST
+
+# Keys defined by HID usages
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+
+# Joystick and game controller axes.
+# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x03 RX
+axis 0x04 RY
+axis 0x05 RZ
+axis 0x06 THROTTLE
+axis 0x07 RUDDER
+axis 0x08 WHEEL
+axis 0x09 GAS
+axis 0x0a BRAKE
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# LEDs
+led 0x00 NUM_LOCK
+led 0x01 CAPS_LOCK
+led 0x02 SCROLL_LOCK
+led 0x03 COMPOSE
+led 0x04 KANA
+led 0x05 SLEEP
+led 0x06 SUSPEND
+led 0x07 MUTE
+led 0x08 MISC
+led 0x09 MAIL
+led 0x0a CHARGING
diff --git a/dirty-image-objects b/dirty-image-objects
new file mode 100644
index 0000000..9b4d199
--- /dev/null
+++ b/dirty-image-objects
@@ -0,0 +1,176 @@
+#
+# 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.
+#
+#
+#
+# Dirty-image-objects file for boot image.
+#
+# Objects in this file are known dirty at runtime. Current this includes:
+#   - classes with known dirty static fields.
+#
+# The image writer will bin these objects together in the image.
+#
+# This file can be generated using imgdiag with a command such as:
+#   adb shell imgdiag --image-diff-pid=<app pid> --zygote-diff-pid=<zygote pid> \
+#     --boot-image=/system/framework/boot.art --dump-dirty-objects
+# Then, grep for lines containing "Private dirty object" from the output.
+# This particular file was generated by dumping systemserver and systemui.
+#
+java.lang.System
+java.net.Inet4Address
+java.lang.Thread
+java.lang.Throwable
+java.util.Collections
+javax.net.ssl.SSLContext
+java.nio.charset.Charset
+java.security.Provider
+javax.net.ssl.HttpsURLConnection
+javax.net.ssl.SSLSocketFactory
+java.util.TimeZone
+java.util.Locale
+java.util.function.ToIntFunction
+sun.misc.FormattedFloatingDecimal
+java.util.stream.IntStream
+android.icu.util.TimeZone
+libcore.io.DropBox
+org.apache.harmony.luni.internal.util.TimezoneGetter
+dalvik.system.SocketTagger
+dalvik.system.CloseGuard
+java.lang.ref.FinalizerReference
+com.android.org.conscrypt.ct.CTLogStoreImpl
+com.android.org.conscrypt.SSLParametersImpl
+com.android.org.conscrypt.OpenSSLContextImpl
+com.android.org.conscrypt.SSLParametersImpl$AliasChooser
+com.android.org.conscrypt.SSLParametersImpl$PSKCallbacks
+com.android.org.conscrypt.NativeCrypto$SSLHandshakeCallbacks
+com.android.okhttp.OkHttpClient
+com.android.okhttp.okio.SegmentPool
+com.android.okhttp.okio.AsyncTimeout
+com.android.okhttp.HttpUrl
+android.os.StrictMode
+com.android.internal.os.BinderInternal
+android.os.storage.StorageManager
+android.os.Trace
+android.app.ActivityManager
+android.media.MediaRouter
+android.os.Environment
+android.view.ThreadedRenderer
+android.media.AudioManager
+android.app.AlarmManager
+android.telephony.TelephonyManager
+android.bluetooth.BluetoothAdapter
+com.android.internal.os.SomeArgs
+android.os.LocaleList
+android.view.WindowManagerGlobal
+android.media.AudioSystem
+android.ddm.DdmHandleAppName
+android.provider.Settings
+android.view.ViewRootImpl
+android.net.ConnectivityManager
+android.app.ActivityThread
+android.os.BaseBundle
+android.util.ArraySet
+android.view.View
+android.os.ServiceManager
+android.view.ViewTreeObserver
+android.hardware.input.InputManager
+android.os.UEventObserver
+android.app.NotificationManager
+android.hardware.display.DisplayManagerGlobal
+android.os.Binder
+android.app.AppOpsManager
+android.content.ContentResolver
+android.app.backup.BackupManager
+android.util.ArrayMap
+android.os.Looper
+android.graphics.Bitmap
+android.view.textservice.TextServicesManager
+com.android.internal.inputmethod.InputMethodUtils
+android.app.QueuedWork
+android.graphics.TemporaryBuffer
+android.widget.ImageView
+android.database.sqlite.SQLiteGlobal
+android.view.autofill.Helper
+android.text.method.SingleLineTransformationMethod
+com.android.internal.os.RuntimeInit
+android.view.inputmethod.InputMethodManager
+android.hardware.SystemSensorManager
+android.database.CursorWindow
+android.text.TextUtils
+android.media.PlayerBase
+android.app.ResourcesManager
+android.os.Message
+android.view.accessibility.AccessibilityManager
+android.app.Notification
+android.provider.ContactsContract$ContactNameColumns
+android.provider.CalendarContract$EventsColumns
+android.provider.CalendarContract$CalendarColumns
+android.provider.CalendarContract$SyncColumns
+android.provider.ContactsContract$ContactsColumns
+android.content.pm.PackageManager$OnPermissionsChangedListener
+android.net.IpConfiguration$ProxySettings
+android.provider.ContactsContract$ContactOptionsColumns
+android.net.wifi.SupplicantState
+android.provider.ContactsContract$ContactStatusColumns
+android.view.accessibility.AccessibilityManager$TouchExplorationStateChangeListener
+android.provider.CalendarContract$CalendarSyncColumns
+android.bluetooth.BluetoothProfile$ServiceListener
+android.provider.ContactsContract$ContactCounts
+android.net.IpConfiguration$IpAssignment
+android.text.TextWatcher
+android.graphics.Bitmap$CompressFormat
+android.location.LocationListener
+sun.security.jca.Providers
+java.lang.CharSequence
+android.icu.util.ULocale
+dalvik.system.BaseDexClassLoader
+android.icu.text.BreakIterator
+libcore.io.EventLogger
+libcore.net.NetworkSecurityPolicy
+android.icu.text.UnicodeSet
+com.android.org.conscrypt.TrustedCertificateStore$PreloadHolder
+android.app.SearchManager
+android.os.Build
+android.app.ContextImpl
+android.app.WallpaperManager
+android.security.net.config.ApplicationConfig
+android.animation.LayoutTransition
+android.widget.TextView
+com.android.internal.logging.MetricsLogger
+android.renderscript.RenderScriptCacheDir
+android.os.Process
+android.os.Handler
+android.content.Context
+android.graphics.drawable.AdaptiveIconDrawable
+android.provider.FontsContract
+android.text.style.SuggestionSpan
+android.graphics.drawable.VectorDrawable$VGroup
+android.view.ViewStub
+android.text.style.MetricAffectingSpan
+android.content.SharedPreferences$OnSharedPreferenceChangeListener
+android.app.PendingIntent
+android.text.SpanWatcher
+android.widget.FrameLayout
+android.net.NetworkRequest$Type
+android.net.NetworkInfo$State
+android.graphics.drawable.GradientDrawable
+android.text.style.AlignmentSpan
+android.widget.LinearLayout
+android.text.style.CharacterStyle
+android.view.View$OnApplyWindowInsetsListener
+android.view.MenuItem
+android.text.style.ReplacementSpan
+android.graphics.drawable.Icon
+android.widget.Button
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 16eab45..aa9227c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2747,7 +2747,7 @@
      * @param offset index of caret position
      * @return width measurement between start and offset
      */
-    public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
+    public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, int offset) {
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 1d8b583..3a8dfb0 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -548,29 +548,7 @@
             final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
             final boolean italic =
                     (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
-            final int key = weight << 1 | (italic ? 1 : 0);
-
-            Typeface typeface;
-            synchronized(sLock) {
-                SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
-                if (innerCache != null) {
-                    typeface = innerCache.get(key);
-                    if (typeface != null) {
-                        return typeface;
-                    }
-                }
-
-                typeface = new Typeface(
-                        nativeCreateFromTypefaceWithExactStyle(
-                                base.native_instance, weight, italic));
-
-                if (innerCache == null) {
-                    innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
-                    sTypefaceCache.put(base.native_instance, innerCache);
-                }
-                innerCache.put(key, typeface);
-            }
-            return typeface;
+            return createWeightStyle(base, weight, italic);
         }
 
         /**
@@ -688,7 +666,8 @@
      * style from the same family of an existing typeface object. If family is
      * null, this selects from the default font's family.
      *
-     * @param family May be null. The name of the existing type face.
+     * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+     *               typeface is used instead.
      * @param style  The style (normal, bold, italic) of the typeface.
      *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
      * @return The best matching typeface.
@@ -727,6 +706,53 @@
         return typeface;
     }
 
+    /**
+     * Creates a typeface object that best matches the specified existing typeface and the specified
+     * weight and italic style
+     *
+     * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+     *               typeface is used instead.
+     * @param weight The desired weight to be drawn.
+     * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
+     * @return A {@link Typeface} object for drawing specified weight and italic style. Never
+     *         returns {@code null}
+     */
+    public static @NonNull Typeface create(@Nullable Typeface family,
+            @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+        Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
+        if (family == null) {
+            family = sDefaultTypeface;
+        }
+        return createWeightStyle(family, weight, italic);
+    }
+
+    private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
+            @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+        final int key = weight << 1 | (italic ? 1 : 0);
+
+        Typeface typeface;
+        synchronized(sLock) {
+            SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
+            if (innerCache != null) {
+                typeface = innerCache.get(key);
+                if (typeface != null) {
+                    return typeface;
+                }
+            }
+
+            typeface = new Typeface(
+                    nativeCreateFromTypefaceWithExactStyle(
+                            base.native_instance, weight, italic));
+
+            if (innerCache == null) {
+                innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
+                sTypefaceCache.put(base.native_instance, innerCache);
+            }
+            innerCache.put(key, typeface);
+        }
+        return typeface;
+    }
+
     /** @hide */
     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
             @NonNull List<FontVariationAxis> axes) {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 55eeb7f..61b3876 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -241,7 +241,8 @@
     if (CC_LIKELY(layerType != LayerType::RenderLayer)
             || CC_UNLIKELY(!isRenderable())
             || CC_UNLIKELY(properties().getWidth() == 0)
-            || CC_UNLIKELY(properties().getHeight() == 0)) {
+            || CC_UNLIKELY(properties().getHeight() == 0)
+            || CC_UNLIKELY(!properties().fitsOnLayer())) {
         if (CC_UNLIKELY(hasLayer())) {
             renderthread::CanvasContext::destroyLayer(this);
         }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 02c0e19..9683d06 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -25,6 +25,7 @@
 
 #include <SkCanvasStateUtils.h>
 #include <SkColorFilter.h>
+// TODO remove me!
 #include <SkColorSpaceXformCanvas.h>
 #include <SkDrawable.h>
 #include <SkDeque.h>
@@ -47,28 +48,25 @@
     return new SkiaCanvas(bitmap);
 }
 
-Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) {
-    return new SkiaCanvas(skiaCanvas);
+Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB) {
+    return new SkiaCanvas(skiaCanvas, xformToSRGB);
 }
 
 SkiaCanvas::SkiaCanvas() {}
 
-SkiaCanvas::SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {}
+SkiaCanvas::SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB)
+    : mCanvas(canvas)
+{
+    LOG_ALWAYS_FATAL_IF(XformToSRGB::kImmediate == xformToSRGB);
+}
 
 SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
     sk_sp<SkColorSpace> cs = bitmap.refColorSpace();
     mCanvasOwned =
             std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy));
-    if (cs.get() == nullptr || cs->isSRGB()) {
-        mCanvas = mCanvasOwned.get();
-    } else {
-        /** The wrapper is needed if we are drawing into a non-sRGB destination, since
-         *  we need to transform all colors (not just bitmaps via filters) into the
-         *  destination's colorspace.
-         */
-        mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), std::move(cs));
-        mCanvas = mCanvasWrapper.get();
-    }
+    mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(),
+            cs == nullptr ? SkColorSpace::MakeSRGB() : std::move(cs));
+    mCanvas = mCanvasWrapper.get();
 }
 
 SkiaCanvas::~SkiaCanvas() {}
@@ -77,7 +75,6 @@
     if (mCanvas != skiaCanvas) {
         mCanvas = skiaCanvas;
         mCanvasOwned.reset();
-        mCanvasWrapper.reset();
     }
     mSaveStack.reset(nullptr);
     mHighContrastText = false;
@@ -91,15 +88,13 @@
     sk_sp<SkColorSpace> cs = bitmap.refColorSpace();
     std::unique_ptr<SkCanvas> newCanvas =
             std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy));
-    std::unique_ptr<SkCanvas> newCanvasWrapper;
-    if (cs.get() != nullptr && !cs->isSRGB()) {
-        newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(), std::move(cs));
-    }
+    std::unique_ptr<SkCanvas> newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(),
+            cs == nullptr ? SkColorSpace::MakeSRGB() : std::move(cs));
 
     // deletes the previously owned canvas (if any)
     mCanvasOwned = std::move(newCanvas);
     mCanvasWrapper = std::move(newCanvasWrapper);
-    mCanvas = mCanvasWrapper ? mCanvasWrapper.get() : mCanvasOwned.get();
+    mCanvas = mCanvasWrapper.get();
 
     // clean up the old save stack
     mSaveStack.reset(nullptr);
@@ -536,27 +531,13 @@
 // Canvas draw operations: Bitmaps
 // ----------------------------------------------------------------------------
 
-const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
-        sk_sp<SkColorFilter> colorSpaceFilter) {
-    /* We don't apply the colorSpace filter if this canvas is already wrapped with
-     * a SkColorSpaceXformCanvas since it already takes care of converting the
-     * contents of the bitmap into the appropriate colorspace.  The mCanvasWrapper
-     * should only be used if this canvas is backed by a surface/bitmap that is known
-     * to have a non-sRGB colorspace.
-     */
-    if (!mCanvasWrapper && colorSpaceFilter) {
+inline static const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
+        sk_sp<SkColorFilter> colorFilter) {
+    if (colorFilter) {
         if (origPaint) {
             *tmpPaint = *origPaint;
         }
-
-        if (tmpPaint->getColorFilter()) {
-            tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter(
-                    tmpPaint->refColorFilter(), colorSpaceFilter));
-            LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
-        } else {
-            tmpPaint->setColorFilter(colorSpaceFilter);
-        }
-
+        tmpPaint->setColorFilter(colorFilter);
         return tmpPaint;
     } else {
         return origPaint;
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 6a01f96..af2c23e 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -37,8 +37,12 @@
      *  @param canvas SkCanvas to handle calls made to this SkiaCanvas. Must
      *      not be NULL. This constructor does not take ownership, so the caller
      *      must guarantee that it remains valid while the SkiaCanvas is valid.
+     *  @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+     *      color space before drawing.  This makes sense for software rendering.
+     *      For the picture case, it may make more sense to leave bitmaps as is,
+     *      and handle the xform when replaying the picture.
      */
-    explicit SkiaCanvas(SkCanvas* canvas);
+    explicit SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB);
 
     virtual ~SkiaCanvas();
 
@@ -178,9 +182,6 @@
     void drawPoints(const float* points, int count, const SkPaint& paint,
             SkCanvas::PointMode mode);
 
-    const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
-            sk_sp<SkColorFilter> colorSpaceFilter);
-
     class Clip;
 
     std::unique_ptr<SkCanvas> mCanvasWrapper; // might own a wrapper on the canvas
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 90d98f2..ac8a081 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -98,6 +98,15 @@
     static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height,
             uirenderer::RenderNode* renderNode = nullptr);
 
+    enum class XformToSRGB {
+        // Transform any Bitmaps to the sRGB color space before drawing.
+        kImmediate,
+
+        // Draw the Bitmap as is.  This likely means that we are recording and that the
+        // transform can be handled at playback time.
+        kDefer,
+    };
+
     /**
      *  Create a new Canvas object which delegates to an SkCanvas.
      *
@@ -105,10 +114,12 @@
      *      delegated to this object. This function will call ref() on the
      *      SkCanvas, and the returned Canvas will unref() it upon
      *      destruction.
+     *  @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+     *      color space before drawing.
      *  @return new non-null Canvas Object.  The type of DisplayList produced by this canvas is
      *      determined based on  Properties::getRenderPipelineType().
      */
-    static Canvas* create_canvas(SkCanvas* skiaCanvas);
+    static Canvas* create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB);
 
     /**
      *  Provides a Skia SkCanvas interface that acts as a proxy to this Canvas.
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index ea302a1..fcd72af 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -17,6 +17,7 @@
 #include "GLFunctorDrawable.h"
 #include "GlFunctorLifecycleListener.h"
 #include "RenderNode.h"
+#include "SkAndroidFrameworkUtils.h"
 #include "SkClipStack.h"
 #include <private/hwui/DrawGlInfo.h>
 #include <GrContext.h>
@@ -49,8 +50,6 @@
         return;
     }
 
-    canvas->flush();
-
     if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
         canvas->clear(SK_ColorRED);
         return;
@@ -72,33 +71,33 @@
     info.height = canvasInfo.height();
     mat4.asColMajorf(&info.transform[0]);
 
+    bool clearStencilAfterFunctor = false;
+
     //apply a simple clip with a scissor or a complex clip with a stencil
     SkRegion clipRegion;
     canvas->temporary_internal_getRgnClip(&clipRegion);
     if (CC_UNLIKELY(clipRegion.isComplex())) {
-        //It is only a temporary solution to use a scissor to draw the stencil.
-        //There is a bug 31489986 to implement efficiently non-rectangular clips.
         glDisable(GL_SCISSOR_TEST);
-        glDisable(GL_STENCIL_TEST);
-        glStencilMask(0xff);
+        glStencilMask(0x1);
         glClearStencil(0);
         glClear(GL_STENCIL_BUFFER_BIT);
-        glEnable(GL_SCISSOR_TEST);
-        SkRegion::Cliperator it(clipRegion, ibounds);
-        while (!it.done()) {
-            setScissor(info.height, it.rect());
-            glClearStencil(0x1);
-            glClear(GL_STENCIL_BUFFER_BIT);
-            it.next();
+        bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(canvas);
+        canvas->flush();
+        if (stencilWritten) {
+            glStencilMask(0x1);
+            glStencilFunc(GL_EQUAL, 0x1, 0x1);
+            glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+            clearStencilAfterFunctor = true;
+            glEnable(GL_STENCIL_TEST);
+        } else {
+            glDisable(GL_STENCIL_TEST);
         }
-        glDisable(GL_SCISSOR_TEST);
-        glStencilFunc(GL_EQUAL, 0x1, 0xff);
-        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-        glEnable(GL_STENCIL_TEST);
     } else if (clipRegion.isEmpty()) {
+        canvas->flush();
         glDisable(GL_STENCIL_TEST);
         glDisable(GL_SCISSOR_TEST);
     } else {
+        canvas->flush();
         glDisable(GL_STENCIL_TEST);
         glEnable(GL_SCISSOR_TEST);
         setScissor(info.height, clipRegion.getBounds());
@@ -106,6 +105,15 @@
 
     (*mFunctor)(DrawGlInfo::kModeDraw, &info);
 
+    if (clearStencilAfterFunctor) {
+        //clear stencil buffer as it may be used by Skia
+        glDisable(GL_SCISSOR_TEST);
+        glDisable(GL_STENCIL_TEST);
+        glStencilMask(0x1);
+        glClearStencil(0);
+        glClear(GL_STENCIL_BUFFER_BIT);
+    }
+
     canvas->getGrContext()->resetContext();
  }
 
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 4c1d673..bb41607 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -150,17 +150,8 @@
         if (origPaint) {
             *tmpPaint = *origPaint;
         }
-
-        sk_sp<SkColorFilter> filter;
-        if (colorFilter && tmpPaint->getColorFilter()) {
-            filter = SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorFilter);
-            LOG_ALWAYS_FATAL_IF(!filter);
-        } else {
-            filter = colorFilter;
-        }
-
         tmpPaint->setAntiAlias(false);
-        tmpPaint->setColorFilter(filter);
+        tmpPaint->setColorFilter(colorFilter);
         return tmpPaint;
     } else {
         return origPaint;
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index d84b83d..c048dda 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -45,7 +45,8 @@
         // record the same text draw into a SkPicture and replay it into a Recording canvas
         SkPictureRecorder recorder;
         SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
-        std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
+        std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas,
+                Canvas::XformToSRGB::kDefer));
         TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
         sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
 
@@ -64,7 +65,7 @@
 
 TEST(SkiaCanvas, drawShadowLayer) {
     auto surface = SkSurface::MakeRasterN32Premul(10, 10);
-    SkiaCanvas canvas(surface->getCanvas());
+    SkiaCanvas canvas(surface->getCanvas(), Canvas::XformToSRGB::kDefer);
 
     // clear to white
     canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
@@ -107,14 +108,27 @@
     // The result should be less than fully red, since we convert to Adobe RGB at draw time.
     ASSERT_EQ(0xFF0000DC, *adobeSkBitmap.getAddr32(0, 0));
 
-    // Test picture recording.
+    // Now try in kDefer mode.  This is a little strange given that, in practice, all software
+    // canvases are kImmediate.
+    SkCanvas skCanvas(skBitmap);
+    SkiaCanvas deferCanvas(&skCanvas, Canvas::XformToSRGB::kDefer);
+    deferCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
+    // The result should be as before, since we deferred the conversion to sRGB.
+    ASSERT_EQ(0xFF0000DC, *skBitmap.getAddr32(0, 0));
+
+    // Test picture recording.  We will kDefer the xform at recording time, but handle it when
+    // we playback to the software canvas.
     SkPictureRecorder recorder;
     SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
-    SkiaCanvas picCanvas(skPicCanvas);
+    SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer);
     picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
     sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
 
-    // Playback to an software canvas.  The result should be fully red.
+    // Playback to a deferred canvas.  The result should be as before.
+    deferCanvas.asSkCanvas()->drawPicture(picture);
+    ASSERT_EQ(0xFF0000DC, *skBitmap.getAddr32(0, 0));
+
+    // Playback to an immediate canvas.  The result should be fully red.
     canvas.asSkCanvas()->drawPicture(picture);
     ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0));
 }
@@ -141,7 +155,7 @@
     // Create a picture canvas.
     SkPictureRecorder recorder;
     SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
-    SkiaCanvas picCanvas(skPicCanvas);
+    SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer);
     state = picCanvas.captureCanvasState();
 
     // Verify that we cannot get the CanvasState.
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 7cdde5f..4c3e261 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2102,9 +2102,7 @@
     private int getMimeType(BufferedInputStream in) throws IOException {
         in.mark(SIGNATURE_CHECK_SIZE);
         byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
-        if (in.read(signatureCheckBytes) != SIGNATURE_CHECK_SIZE) {
-            throw new EOFException();
-        }
+        in.read(signatureCheckBytes);
         in.reset();
         if (isJpegFormat(signatureCheckBytes)) {
             return IMAGE_TYPE_JPEG;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7dd70d4..6664456 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -246,7 +246,7 @@
      * {@link #OPTION_CLOSEST} often has larger performance overhead compared
      * to the other options if there is no sync frame located at timeUs.
      *
-     * @return A Bitmap containing a representative video frame, which 
+     * @return A Bitmap containing a representative video frame, which
      *         can be null, if such a frame cannot be retrieved.
      */
     public Bitmap getFrameAtTime(long timeUs, int option) {
@@ -255,7 +255,58 @@
             throw new IllegalArgumentException("Unsupported option: " + option);
         }
 
-        return _getFrameAtTime(timeUs, option);
+        return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/);
+    }
+
+    /**
+     * Retrieve a video frame near a given timestamp scaled to a desired size.
+     * Call this method after setDataSource(). This method finds a representative
+     * frame close to the given time position by considering the given option
+     * if possible, and returns it as a bitmap with same aspect ratio as the source
+     * while scaling it so that it fits into the desired size of dst_width by dst_height.
+     * This is useful for generating a thumbnail for an input data source or just to
+     * obtain a scaled frame at the given time position.
+     *
+     * @param timeUs The time position in microseconds where the frame will be retrieved.
+     * When retrieving the frame at the given time position, there is no
+     * guarantee that the data source has a frame located at the position.
+     * When this happens, a frame nearby will be returned. If timeUs is
+     * negative, time position and option will ignored, and any frame
+     * that the implementation considers as representative may be returned.
+     *
+     * @param option a hint on how the frame is found. Use
+     * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp earlier than or the same as timeUs. Use
+     * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp later than or the same as timeUs. Use
+     * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+     * that has a timestamp closest to or the same as timeUs. Use
+     * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+     * or may not be a sync frame but is closest to or the same as timeUs.
+     * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+     * to the other options if there is no sync frame located at timeUs.
+     *
+     * @param dst_width expected output bitmap width
+     * @param dst_height expected output bitmap height
+     * @return A Bitmap of size not larger than dst_width by dst_height containing a
+     *         scaled video frame, which can be null, if such a frame cannot be retrieved.
+     * @throws IllegalArgumentException if passed in invalid option or width by height
+     *         is less than or equal to 0.
+     */
+    public Bitmap getScaledFrameAtTime(
+            long timeUs, int option, int dst_width, int dst_height) {
+        if (option < OPTION_PREVIOUS_SYNC ||
+            option > OPTION_CLOSEST) {
+            throw new IllegalArgumentException("Unsupported option: " + option);
+        }
+        if (dst_width <= 0) {
+            throw new IllegalArgumentException("Invalid width: " + dst_width);
+        }
+        if (dst_height <= 0) {
+            throw new IllegalArgumentException("Invalid height: " + dst_height);
+        }
+
+        return _getFrameAtTime(timeUs, option, dst_width, dst_height);
     }
 
     /**
@@ -273,8 +324,8 @@
      * negative, time position and option will ignored, and any frame
      * that the implementation considers as representative may be returned.
      *
-     * @return A Bitmap containing a representative video frame, which
-     *         can be null, if such a frame cannot be retrieved.
+     * @return A Bitmap of size dst_widthxdst_height containing a representative
+     *         video frame, which can be null, if such a frame cannot be retrieved.
      *
      * @see #getFrameAtTime(long, int)
      */
@@ -297,17 +348,16 @@
      * @see #getFrameAtTime(long, int)
      */
     public Bitmap getFrameAtTime() {
-        return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
+        return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/);
     }
 
-    private native Bitmap _getFrameAtTime(long timeUs, int option);
+    private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
 
-    
     /**
      * Call this method after setDataSource(). This method finds the optional
      * graphic or album/cover art associated associated with the data source. If
      * there are more than one pictures, (any) one of them is returned.
-     * 
+     *
      * @return null if no such graphic is found.
      */
     public byte[] getEmbeddedPicture() {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 849bd01..5a16c36 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -73,6 +73,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.net.CookieHandler;
+import java.net.CookieManager;
 import java.net.HttpCookie;
 import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
@@ -1004,19 +1006,28 @@
     /**
      * Sets the data source as a content Uri.
      *
-     * @param context the Context to use when resolving the Uri
-     * @param uri the Content URI of the data you want to play
-     * @param headers the headers to be sent together with the request for the data
-     *                The headers must not include cookies. Instead, use the cookies param.
-     * @param cookies the cookies to be sent together with the request
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws NullPointerException  if context or uri is null
-     * @throws IOException           if uri has a file scheme and an I/O error occurs
+     * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
+     * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
+     * this API to pass the cookies as a list of HttpCookie. If the app has not installed
+     * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
+     * the provided cookies. If the app has installed its own handler already, this API requires the
+     * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
      *
      * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
      * but that can be changed with key/value pairs through the headers parameter with
      * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
      * disallow or allow cross domain redirection.
+     *
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @param headers the headers to be sent together with the request for the data
+     *                The headers must not include cookies. Instead, use the cookies param.
+     * @param cookies the cookies to be sent together with the request
+     * @throws IllegalArgumentException if cookies are provided and the installed handler is not
+     *                                  a CookieManager
+     * @throws IllegalStateException    if it is called in an invalid state
+     * @throws NullPointerException     if context or uri is null
+     * @throws IOException              if uri has a file scheme and an I/O error occurs
      */
     public void setDataSource(@NonNull Context context, @NonNull Uri uri,
             @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
@@ -1029,6 +1040,14 @@
             throw new NullPointerException("uri param can not be null.");
         }
 
+        if (cookies != null) {
+            CookieHandler cookieHandler = CookieHandler.getDefault();
+            if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
+                throw new IllegalArgumentException("The cookie handler has to be of CookieManager "
+                        + "type when cookies are provided.");
+            }
+        }
+
         // The context and URI usually belong to the calling user. Get a resolver for that user
         // and strip out the userId from the URI if present.
         final ContentResolver resolver = context.getContentResolver();
@@ -1064,15 +1083,15 @@
     /**
      * Sets the data source as a content Uri.
      *
-     * @param context the Context to use when resolving the Uri
-     * @param uri the Content URI of the data you want to play
-     * @param headers the headers to be sent together with the request for the data
-     * @throws IllegalStateException if it is called in an invalid state
-     *
      * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
      * but that can be changed with key/value pairs through the headers parameter with
      * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
      * disallow or allow cross domain redirection.
+     *
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @param headers the headers to be sent together with the request for the data
+     * @throws IllegalStateException if it is called in an invalid state
      */
     public void setDataSource(@NonNull Context context, @NonNull Uri uri,
             @Nullable Map<String, String> headers)
@@ -1093,15 +1112,15 @@
     /**
      * Sets the data source (file-path or http/rtsp URL) to use.
      *
-     * @param path the path of the file, or the http/rtsp URL of the stream you want to play
-     * @throws IllegalStateException if it is called in an invalid state
-     *
      * <p>When <code>path</code> refers to a local file, the file may actually be opened by a
      * process other than the calling application.  This implies that the pathname
      * should be an absolute path (as any other process runs with unspecified current working
      * directory), and that the pathname should reference a world-readable file.
      * As an alternative, the application could first open the file for reading,
      * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
+     *
+     * @param path the path of the file, or the http/rtsp URL of the stream you want to play
+     * @throws IllegalStateException if it is called in an invalid state
      */
     public void setDataSource(String path)
             throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 9862564..2bccd88 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -256,7 +256,13 @@
      */
     private void forceCloseConnection() {
         if (mServiceConnection != null) {
-            mContext.unbindService(mServiceConnection);
+            try {
+                mContext.unbindService(mServiceConnection);
+            } catch (IllegalArgumentException e) {
+                if (DBG) {
+                    Log.d(TAG, "unbindService failed", e);
+                }
+            }
         }
         mState = CONNECT_STATE_DISCONNECTED;
         mServiceConnection = null;
@@ -445,6 +451,9 @@
         ResultReceiver receiver = new ResultReceiver(mHandler) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (!isConnected()) {
+                    return;
+                }
                 if (resultCode != 0 || resultData == null
                         || !resultData.containsKey(MediaBrowserService.KEY_MEDIA_ITEM)) {
                     cb.onError(mediaId);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 07cfbda..df87e0f 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -367,7 +367,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             if (mTvInputSessionImpl == null) {
                 // The session has been finished.
                 finishInputEvent(event, false);
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index b52906d..4df645d 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -700,6 +700,13 @@
                 new Result<MediaBrowser.MediaItem>(itemId) {
             @Override
             void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
+                if (mConnections.get(connection.callbacks.asBinder()) != connection) {
+                    if (DBG) {
+                        Log.d(TAG, "Not sending onLoadItem result for connection that has"
+                                + " been disconnected. pkg=" + connection.pkg + " id=" + itemId);
+                    }
+                    return;
+                }
                 if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) {
                     receiver.send(RESULT_ERROR, null);
                     return;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 71f3856..4659ae1 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -244,9 +244,11 @@
     }
 }
 
-static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
+static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
+        JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
 {
-    ALOGV("getFrameAtTime: %lld us option: %d", (long long)timeUs, option);
+    ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
+            (long long)timeUs, option, dst_width, dst_height);
     MediaMetadataRetriever* retriever = getRetriever(env, thiz);
     if (retriever == 0) {
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
@@ -274,15 +276,19 @@
                         fields.createConfigMethod,
                         GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType));
 
-    uint32_t width, height;
+    uint32_t width, height, displayWidth, displayHeight;
     bool swapWidthAndHeight = false;
     if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
         width = videoFrame->mHeight;
         height = videoFrame->mWidth;
         swapWidthAndHeight = true;
+        displayWidth = videoFrame->mDisplayHeight;
+        displayHeight = videoFrame->mDisplayWidth;
     } else {
         width = videoFrame->mWidth;
         height = videoFrame->mHeight;
+        displayWidth = videoFrame->mDisplayWidth;
+        displayHeight = videoFrame->mDisplayHeight;
     }
 
     jobject jBitmap = env->CallStaticObjectMethod(
@@ -308,22 +314,26 @@
            videoFrame->mHeight,
            videoFrame->mRotationAngle);
 
-    if (videoFrame->mDisplayWidth  != videoFrame->mWidth ||
-        videoFrame->mDisplayHeight != videoFrame->mHeight) {
-        uint32_t displayWidth = videoFrame->mDisplayWidth;
-        uint32_t displayHeight = videoFrame->mDisplayHeight;
-        if (swapWidthAndHeight) {
-            displayWidth = videoFrame->mDisplayHeight;
-            displayHeight = videoFrame->mDisplayWidth;
-        }
+    if (dst_width <= 0 || dst_height <= 0) {
+        dst_width = displayWidth;
+        dst_height = displayHeight;
+    } else {
+        float factor = std::min((float)dst_width / (float)displayWidth,
+                (float)dst_height / (float)displayHeight);
+        dst_width = std::round(displayWidth * factor);
+        dst_height = std::round(displayHeight * factor);
+    }
+
+    if ((uint32_t)dst_width != videoFrame->mWidth ||
+        (uint32_t)dst_height != videoFrame->mHeight) {
         ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
-                width, height, displayWidth, displayHeight);
+                width, height, dst_width, dst_height);
         jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
-                                    fields.createScaledBitmapMethod,
-                                    jBitmap,
-                                    displayWidth,
-                                    displayHeight,
-                                    true);
+                                fields.createScaledBitmapMethod,
+                                jBitmap,
+                                dst_width,
+                                dst_height,
+                                true);
         return scaledBitmap;
     }
 
@@ -474,7 +484,7 @@
 
         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
         {"_setDataSource",   "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
-        {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
+        {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 9b5982b9..f243b51 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -34,6 +34,7 @@
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.provider.DocumentsContract;
+import android.provider.MetadataReader;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 
@@ -900,6 +901,9 @@
                 protectionState == MtpConstants.PROTECTION_STATUS_NONE) {
             flag |= Document.FLAG_DIR_SUPPORTS_CREATE;
         }
+        if (MetadataReader.isSupportedMimeType(mimeType)) {
+            flag |= Document.FLAG_SUPPORTS_METADATA;
+        }
         if (thumbnailSize > 0) {
             flag |= Document.FLAG_SUPPORTS_THUMBNAIL;
         }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index eb2d8aa..8c8116b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.mtp;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -37,11 +38,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ProxyFileDescriptorCallback;
 import android.os.storage.StorageManager;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
+import android.provider.MetadataReader;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
@@ -50,14 +52,16 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import libcore.io.IoUtils;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
-import libcore.io.IoUtils;
 
 /**
  * DocumentsProvider for MTP devices.
@@ -107,7 +111,7 @@
         mResources = getContext().getResources();
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
-        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
+        mDeviceToolkits = new HashMap<>();
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
         mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
         mIntentSender = new ServiceIntentSender(getContext());
@@ -151,7 +155,7 @@
         mResources = resources;
         mMtpManager = mtpManager;
         mResolver = resolver;
-        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
+        mDeviceToolkits = new HashMap<>();
         mDatabase = database;
         mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
         mIntentSender = intentSender;
@@ -548,6 +552,29 @@
         }
     }
 
+    @Override
+    public @Nullable Bundle getDocumentMetadata(String docId) throws FileNotFoundException {
+        String mimeType = getDocumentType(docId);
+
+        if (!MetadataReader.isSupportedMimeType(mimeType)) {
+            return null;
+        }
+
+        InputStream stream = null;
+        try {
+            stream = new ParcelFileDescriptor.AutoCloseInputStream(
+                    openDocument(docId, "r", null));
+            Bundle metadata = new Bundle();
+            MetadataReader.getMetadata(metadata, stream, mimeType, null);
+            return metadata;
+        } catch (IOException e) {
+            Log.e(TAG, "An error occurred retrieving the metadata", e);
+            return null;
+        } finally {
+            IoUtils.closeQuietly(stream);
+        }
+    }
+
     void openDevice(int deviceId) throws IOException {
         synchronized (mDeviceListLock) {
             if (mDeviceToolkits.containsKey(deviceId)) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7fe6cb9..32b169e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -237,7 +237,8 @@
         assertEquals(
                 COLUMN_FLAGS,
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
-                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
+                DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
+                DocumentsContract.Document.FLAG_SUPPORTS_METADATA,
                 cursor.getInt(9));
         assertEquals(2 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
         assertEquals(
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 3fa5eb5..65c86df 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -329,7 +329,8 @@
         assertEquals(
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
-                DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
+                DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL |
+                DocumentsContract.Document.FLAG_SUPPORTS_METADATA,
                 cursor.getInt(4));
         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
     }
@@ -474,7 +475,8 @@
         assertEquals("image/jpeg", cursor.getString(1));
         assertEquals("image.jpg", cursor.getString(2));
         assertEquals(0, cursor.getLong(3));
-        assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
+        assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL
+                | Document.FLAG_SUPPORTS_METADATA, cursor.getInt(4));
         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
 
         cursor.close();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index d07da93..7d4bc83 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -97,7 +97,6 @@
 
         // Pairing broadcasts
         addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
-        addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
 
         // Fine-grained state broadcasts
         addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
@@ -344,24 +343,6 @@
         }
     }
 
-    private class PairingCancelHandler implements Handler {
-        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
-            if (device == null) {
-                Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
-                return;
-            }
-            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
-            if (cachedDevice == null) {
-                Log.e(TAG, "ACTION_PAIRING_CANCEL with no cached device");
-                return;
-            }
-            int errorMsg = R.string.bluetooth_pairing_error_message;
-            if (context != null && cachedDevice != null) {
-                Utils.showError(context, cachedDevice.getName(), errorMsg);
-            }
-        }
-    }
-
     private class DockEventHandler implements Handler {
         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
             // Remove if unpair device upon undocking
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 9620a91..fc0a444 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -149,13 +149,6 @@
     public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
 
     /**
-     * @deprecated Use {@link #META_DATA_PREFERENCE_TITLE} with {@code android:resource}
-     */
-    @Deprecated
-    public static final String META_DATA_PREFERENCE_TITLE_RES_ID =
-            "com.android.settings.title.resid";
-
-    /**
      * Name of the meta-data item that should be set in the AndroidManifest.xml
      * to specify the summary text that should be displayed for the preference.
      */
@@ -176,7 +169,7 @@
      * custom view which should be displayed for the preference. The custom view will be inflated
      * as a remote view.
      *
-     * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id
+     * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
      * of the summary TextView to '@android:id/summary'.
      */
     public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
@@ -411,14 +404,7 @@
                                     metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
                         }
                     }
-                    int resId = 0;
-                    if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) {
-                        resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID);
-                        if (resId != 0) {
-                            title = res.getString(resId);
-                        }
-                    }
-                    if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+                    if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
                         if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
                             title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
                         } else {
@@ -464,12 +450,14 @@
             }
 
             // Set the icon
-            if (iconFromUri != null) {
-                tile.icon = Icon.createWithResource(iconFromUri.first, iconFromUri.second);
-            } else {
-                if (icon == 0) {
+            if (icon == 0) {
+                // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
+                // ICON_URI should be loaded in app UI when need the icon object.
+                if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
                     icon = activityInfo.icon;
                 }
+            }
+            if (icon != 0) {
                 tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
             }
 
@@ -492,7 +480,7 @@
 
     /**
      * Gets the icon package name and resource id from content provider.
-     * @param Context context
+     * @param context context
      * @param packageName package name of the target activity
      * @param uriString URI for the content provider
      * @param providerMap Maps URI authorities to providers
@@ -522,7 +510,7 @@
 
     /**
      * Gets text associated with the input key from the content provider.
-     * @param Context context
+     * @param context context
      * @param uriString URI for the content provider
      * @param providerMap Maps URI authorities to providers
      * @param key Key mapping to the text in bundle returned by the content provider
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
index 6eeb593..88adcdb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
@@ -18,10 +18,14 @@
 import android.os.Handler;
 import android.os.Looper;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 public class ThreadUtils {
 
     private static volatile Thread sMainThread;
     private static volatile Handler sMainThreadHandler;
+    private static volatile ExecutorService sSingleThreadExecutor;
 
     /**
      * Returns true if the current thread is the UI thread.
@@ -54,6 +58,16 @@
     }
 
     /**
+     * Posts runnable in background using shared background thread pool.
+     */
+    public static void postOnBackgroundThread(Runnable runnable) {
+        if (sSingleThreadExecutor == null) {
+            sSingleThreadExecutor = Executors.newSingleThreadExecutor();
+        }
+        sSingleThreadExecutor.execute(runnable);
+    }
+
+    /**
      * Posts the runnable on the main thread.
      */
     public static void postOnMainThread(Runnable runnable) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index bffd6cc..33af4039 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -37,6 +37,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
@@ -140,6 +141,9 @@
     static final String KEY_CONFIG = "key_config";
     static final String KEY_FQDN = "key_fqdn";
     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
+    static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
+    static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
+    static final String KEY_CARRIER_NAME = "key_carrier_name";
     static final AtomicInteger sLastId = new AtomicInteger(0);
 
     /**
@@ -197,6 +201,13 @@
     private String mFqdn;
     private String mProviderFriendlyName;
 
+    private boolean mIsCarrierAp = false;
+    /**
+     * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
+     */
+    private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
+    private String mCarrierName = null;
+
     public AccessPoint(Context context, Bundle savedState) {
         mContext = context;
         mConfig = savedState.getParcelable(KEY_CONFIG);
@@ -233,6 +244,15 @@
         if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
             mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
         }
+        if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
+            mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
+        }
+        if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
+            mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
+        }
+        if (savedState.containsKey(KEY_CARRIER_NAME)) {
+            mCarrierName = savedState.getString(KEY_CARRIER_NAME);
+        }
         update(mConfig, mInfo, mNetworkInfo);
         updateRssi();
         updateSeen();
@@ -291,6 +311,9 @@
         this.mId = that.mId;
         this.mSpeed = that.mSpeed;
         this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
+        this.mIsCarrierAp = that.mIsCarrierAp;
+        this.mCarrierApEapType = that.mCarrierApEapType;
+        this.mCarrierName = that.mCarrierName;
     }
 
     /**
@@ -670,6 +693,18 @@
         return null;
     }
 
+    public boolean isCarrierAp() {
+        return mIsCarrierAp;
+    }
+
+    public int getCarrierApEapType() {
+        return mCarrierApEapType;
+    }
+
+    public String getCarrierName() {
+        return mCarrierName;
+    }
+
     public String getSavedNetworkSummary() {
         WifiConfiguration config = mConfig;
         if (config != null) {
@@ -712,6 +747,9 @@
             // This is the active connection on passpoint
             summary.append(getSummary(mContext, getDetailedState(),
                     false, config.providerFriendlyName));
+        } else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
+                && mIsCarrierAp) {
+            summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
         } else if (isActive()) {
             // This is the active connection on non-passpoint network
             summary.append(getSummary(mContext, getDetailedState(),
@@ -745,6 +783,8 @@
             }
         } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
             summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
+        } else if (mIsCarrierAp) {
+            summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
         } else if (!isReachable()) { // Wifi out of range
             summary.append(mContext.getString(R.string.wifi_not_in_range));
         } else { // In range, not disabled.
@@ -1024,6 +1064,9 @@
         mScanResultCache.put(result.BSSID, result);
         updateRssi();
         mSeen = result.timestamp; // even if the timestamp is old it is still valid
+        mIsCarrierAp = result.isCarrierAp;
+        mCarrierApEapType = result.carrierApEapType;
+        mCarrierName = result.carrierName;
     }
 
     public void saveWifiState(Bundle savedState) {
@@ -1045,6 +1088,9 @@
         if (mProviderFriendlyName != null) {
             savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
         }
+        savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
+        savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
+        savedState.putString(KEY_CARRIER_NAME, mCarrierName);
     }
 
     public void setListener(AccessPointListener listener) {
@@ -1073,6 +1119,12 @@
                 mAccessPointListener.onAccessPointChanged(this);
             }
 
+            // The carrier info in the ScanResult is set by the platform based on the SSID and will
+            // always be the same for all matching scan results.
+            mIsCarrierAp = result.isCarrierAp;
+            mCarrierApEapType = result.carrierApEapType;
+            mCarrierName = result.carrierName;
+
             return true;
         }
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 731a607..93bf3c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -53,6 +53,8 @@
     private int mSecurity = AccessPoint.SECURITY_NONE;
     private WifiConfiguration mWifiConfig;
     private WifiInfo mWifiInfo;
+    private boolean mIsCarrierAp = false;
+    private String mCarrierName = null;
 
     Context mContext;
     private ArrayList<ScanResult> mScanResultCache;
@@ -85,6 +87,10 @@
         }
         bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
         bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
+        bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
+        if (mCarrierName != null) {
+            bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
+        }
 
         AccessPoint ap = new AccessPoint(mContext, bundle);
         ap.setRssi(mRssi);
@@ -222,4 +228,14 @@
         mScanResultCache = scanResultCache;
         return this;
     }
+
+    public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
+        mIsCarrierAp = isCarrierAp;
+        return this;
+    }
+
+    public TestAccessPointBuilder setCarrierName(String carrierName) {
+        mCarrierName = carrierName;
+        return this;
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 9645c94..24f0c7a 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -35,6 +35,7 @@
 import android.net.WifiKey;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiSsid;
@@ -491,6 +492,75 @@
                 R.string.wifi_check_password_try_again));
     }
 
+    @Test
+    public void testSummaryString_showsAvaiableViaCarrier() {
+        String carrierName = "Test Carrier";
+        ScanResult result = new ScanResult();
+        result.BSSID = "00:11:22:33:44:55";
+        result.capabilities = "EAP";
+        result.isCarrierAp = true;
+        result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
+        result.carrierName = carrierName;
+
+        AccessPoint ap = new AccessPoint(mContext, result);
+        assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
+                R.string.available_via_carrier), carrierName));
+        assertThat(ap.isCarrierAp()).isEqualTo(true);
+        assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.SIM);
+        assertThat(ap.getCarrierName()).isEqualTo(carrierName);
+    }
+
+    @Test
+    public void testSummaryString_showsConnectedViaCarrier() {
+        int networkId = 123;
+        int rssi = -55;
+        String carrierName = "Test Carrier";
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = networkId;
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.setNetworkId(networkId);
+        wifiInfo.setRssi(rssi);
+
+        NetworkInfo networkInfo =
+                new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+        networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
+
+        AccessPoint ap = new TestAccessPointBuilder(mContext)
+                .setNetworkInfo(networkInfo)
+                .setNetworkId(networkId)
+                .setRssi(rssi)
+                .setWifiInfo(wifiInfo)
+                .setIsCarrierAp(true)
+                .setCarrierName(carrierName)
+                .build();
+        assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
+                R.string.connected_via_carrier), carrierName));
+    }
+
+    @Test
+    public void testUpdateScanResultWithCarrierInfo() {
+        String ssid = "ssid";
+        AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build();
+        assertThat(ap.isCarrierAp()).isEqualTo(false);
+        assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.NONE);
+        assertThat(ap.getCarrierName()).isEqualTo(null);
+
+        int carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
+        String carrierName = "Test Carrier";
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = "00:11:22:33:44:55";
+        scanResult.capabilities = "";
+        scanResult.isCarrierAp = true;
+        scanResult.carrierApEapType = carrierApEapType;
+        scanResult.carrierName = carrierName;
+        assertThat(ap.update(scanResult)).isTrue();
+
+        assertThat(ap.isCarrierAp()).isEqualTo(true);
+        assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType);
+        assertThat(ap.getCarrierName()).isEqualTo(carrierName);
+    }
+
     private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
         Bundle attr1 = new Bundle();
         attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index e9ca753..3e90435 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -535,14 +535,10 @@
             info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri);
         }
         if (titleResId != 0) {
-            info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
+            info.activityInfo.metaData.putInt(TileUtils.META_DATA_PREFERENCE_TITLE, titleResId);
         } else if (title != null) {
             info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
         }
-        if (titleResId != 0) {
-            info.activityInfo.metaData.putInt(
-                    TileUtils.META_DATA_PREFERENCE_TITLE_RES_ID, titleResId);
-        }
         info.activityInfo.applicationInfo = new ApplicationInfo();
         if (systemApp) {
             info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 96f51c1..3d0147d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.BackupUtils;
 import android.util.Log;
 
@@ -62,6 +63,9 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_BACKUP = DEBUG || false;
 
+    private static final byte[] NULL_VALUE = new byte[0];
+    private static final int NULL_SIZE = -1;
+
     private static final String KEY_SYSTEM = "system";
     private static final String KEY_SECURE = "secure";
     private static final String KEY_GLOBAL = "global";
@@ -608,7 +612,7 @@
 
         // Restore only the white list data.
         int pos = 0;
-        Map<String, String> cachedEntries = new HashMap<String, String>();
+        final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
         ContentValues contentValues = new ContentValues(2);
         SettingsHelper settingsHelper = mSettingsHelper;
         ContentResolver cr = getContentResolver();
@@ -616,28 +620,36 @@
         final int whiteListSize = whitelist.length;
         for (int i = 0; i < whiteListSize; i++) {
             String key = whitelist[i];
-            String value = cachedEntries.remove(key);
 
-            // If the value not cached, let us look it up.
-            if (value == null) {
+            String value = null;
+            boolean hasValueToRestore = false;
+            if (cachedEntries.indexOfKey(key) >= 0) {
+                value = cachedEntries.remove(key);
+                hasValueToRestore = true;
+            } else {
+                // If the value not cached, let us look it up.
                 while (pos < bytes) {
                     int length = readInt(settings, pos);
                     pos += INTEGER_BYTE_COUNT;
-                    String dataKey = length > 0 ? new String(settings, pos, length) : null;
+                    String dataKey = length >= 0 ? new String(settings, pos, length) : null;
                     pos += length;
                     length = readInt(settings, pos);
                     pos += INTEGER_BYTE_COUNT;
-                    String dataValue = length > 0 ? new String(settings, pos, length) : null;
-                    pos += length;
+                    String dataValue = null;
+                    if (length >= 0) {
+                        dataValue = new String(settings, pos, length);
+                        pos += length;
+                    }
                     if (key.equals(dataKey)) {
                         value = dataValue;
+                        hasValueToRestore = true;
                         break;
                     }
                     cachedEntries.put(dataKey, dataValue);
                 }
             }
 
-            if (value == null) {
+            if (!hasValueToRestore) {
                 continue;
             }
 
@@ -724,50 +736,56 @@
      * @return The byte array of extracted values.
      */
     private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
-        final int settingsCount = settings.length;
-        byte[][] values = new byte[settingsCount * 2][]; // keys and values
         if (!cursor.moveToFirst()) {
             Log.e(TAG, "Couldn't read from the cursor");
             return new byte[0];
         }
 
+        final int nameColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+        final int valueColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+
         // Obtain the relevant data in a temporary array.
         int totalSize = 0;
         int backedUpSettingIndex = 0;
-        Map<String, String> cachedEntries = new HashMap<String, String>();
+        final int settingsCount = settings.length;
+        final byte[][] values = new byte[settingsCount * 2][]; // keys and values
+        final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
         for (int i = 0; i < settingsCount; i++) {
-            String key = settings[i];
-            String value = cachedEntries.remove(key);
-
-            final int nameColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
-            final int valueColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+            final String key = settings[i];
 
             // If the value not cached, let us look it up.
-            if (value == null) {
+            String value = null;
+            boolean hasValueToBackup = false;
+            if (cachedEntries.indexOfKey(key) >= 0) {
+                value = cachedEntries.remove(key);
+                hasValueToBackup = true;
+            } else {
                 while (!cursor.isAfterLast()) {
-                    String cursorKey = cursor.getString(nameColumnIndex);
-                    String cursorValue = cursor.getString(valueColumnIndex);
+                    final String cursorKey = cursor.getString(nameColumnIndex);
+                    final String cursorValue = cursor.getString(valueColumnIndex);
                     cursor.moveToNext();
                     if (key.equals(cursorKey)) {
                         value = cursorValue;
+                        hasValueToBackup = true;
                         break;
                     }
                     cachedEntries.put(cursorKey, cursorValue);
                 }
             }
 
+            if (!hasValueToBackup) {
+                continue;
+            }
+
             // Intercept the keys and see if they need special handling
             value = mSettingsHelper.onBackupValue(key, value);
 
-            if (value == null) {
-                continue;
-            }
             // Write the key and value in the intermediary array.
-            byte[] keyBytes = key.getBytes();
+            final byte[] keyBytes = key.getBytes();
             totalSize += INTEGER_BYTE_COUNT + keyBytes.length;
             values[backedUpSettingIndex * 2] = keyBytes;
 
-            byte[] valueBytes = value.getBytes();
+            final byte[] valueBytes = (value != null) ? value.getBytes() : NULL_VALUE;
             totalSize += INTEGER_BYTE_COUNT + valueBytes.length;
             values[backedUpSettingIndex * 2 + 1] = valueBytes;
 
@@ -783,8 +801,13 @@
         int pos = 0;
         final int keyValuePairCount = backedUpSettingIndex * 2;
         for (int i = 0; i < keyValuePairCount; i++) {
-            pos = writeInt(result, pos, values[i].length);
-            pos = writeBytes(result, pos, values[i]);
+            final byte[] value = values[i];
+            if (value != NULL_VALUE) {
+                pos = writeInt(result, pos, value.length);
+                pos = writeBytes(result, pos, value);
+            } else {
+                pos = writeInt(result, pos, NULL_SIZE);
+            }
         }
         return result;
     }
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index 27caf96..e38b482 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -8,5 +8,5 @@
    -->
     <path
         android:pathData="M17.32,5.06h-2.91V3.6c0,-0.81 -0.65,-1.46 -1.46,-1.46h-2.91c-0.81,0 -1.46,0.65 -1.46,1.46v1.46H5.68c-0.81,0 -1.45,0.65 -1.45,1.46l-0.01,8c0,0.81 0.65,1.46 1.46,1.46h11.64c0.81,0 1.46,-0.65 1.46,-1.46v-8C18.78,5.7 18.13,5.06 17.32,5.06zM11.5,11.6c-0.8,0 -1.46,-0.65 -1.46,-1.46c0,-0.8 0.65,-1.46 1.46,-1.46s1.46,0.65 1.46,1.46C12.96,10.95 12.3,11.6 11.5,11.6zM12.96,5.06h-2.91V3.6h2.91V5.06z"
-        android:fillColor="#FFFFFF"/>
-</vector>
\ No newline at end of file
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 054d520..f72f379 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -116,11 +116,11 @@
     <color name="segmented_buttons_background">#14FFFFFF</color><!-- 8% white -->
 
     <color name="dark_mode_icon_color_single_tone">#99000000</color>
-    <color name="dark_mode_icon_color_dual_tone_background">#4d000000</color>
+    <color name="dark_mode_icon_color_dual_tone_background">#3d000000</color>
     <color name="dark_mode_icon_color_dual_tone_fill">#7a000000</color>
 
     <color name="light_mode_icon_color_single_tone">#ffffff</color>
-    <color name="light_mode_icon_color_dual_tone_background">#5dffffff</color>
+    <color name="light_mode_icon_color_dual_tone_background">#4dffffff</color>
     <color name="light_mode_icon_color_dual_tone_fill">#ffffff</color>
 
     <color name="volume_settings_icon_color">#7fffffff</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5917dc5..506c294 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -277,6 +277,18 @@
         <item>28</item> <!-- 4: SUN -->
     </integer-array>
 
+    <!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
+               to an opacity value for a black scrim that is overlayed in AOD1.
+               Valid range is from 0 (transparent) to 255 (opaque).
+               -1 means keeping the current opacity. -->
+    <integer-array name="config_doze_brightness_sensor_to_scrim_opacity">
+        <item>-1</item> <!-- 0: OFF -->
+        <item>0</item> <!-- 1: NIGHT -->
+        <item>0</item> <!-- 2: LOW -->
+        <item>0</item> <!-- 3: HIGH -->
+        <item>0</item> <!-- 4: SUN -->
+    </integer-array>
+
     <!-- Doze: whether the double tap sensor reports 2D touch coordinates -->
     <bool name="doze_double_tap_reports_touch_coordinates">false</bool>
 
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e5c729f..9c1cb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -84,6 +85,7 @@
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerServiceImpl;
+import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
@@ -156,6 +158,9 @@
         mProviders.put(ActivityStarterDelegate.class, () ->
                 getDependency(ActivityStarter.class));
 
+        mProviders.put(AsyncSensorManager.class, () ->
+                new AsyncSensorManager(mContext.getSystemService(SensorManager.class)));
+
         mProviders.put(BluetoothController.class, () ->
                 new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
 
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index 40ea4ec..37e2402 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -119,7 +119,7 @@
                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
-                | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
+                | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
         lp.setTitle("RoundedOverlay");
         lp.gravity = Gravity.TOP;
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index e92ed2f..e4b405f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -35,6 +35,7 @@
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.analytics.DataCollector;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.util.AsyncSensorManager;
 
 import java.io.PrintWriter;
 
@@ -87,7 +88,7 @@
 
     private FalsingManager(Context context) {
         mContext = context;
-        mSensorManager = mContext.getSystemService(SensorManager.class);
+        mSensorManager = Dependency.get(AsyncSensorManager.class);
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDataCollector = DataCollector.getInstance(mContext);
         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 9ba7be8..34c05a5 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 import android.view.IWallpaperVisibilityListener;
@@ -31,6 +33,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.types.ExtractionType;
 import com.android.internal.colorextraction.types.Tonal;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 
 import java.io.FileDescriptor;
@@ -75,6 +78,14 @@
                 Log.w(TAG, "Can't listen to wallpaper visibility changes", e);
             }
         }
+
+        WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+        if (wallpaperManager != null) {
+            // Listen to all users instead of only the current one.
+            wallpaperManager.removeOnColorsChangedListener(this);
+            wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
+                    UserHandle.USER_ALL);
+        }
     }
 
     private void updateDefaultGradients(WallpaperColors colors) {
@@ -82,7 +93,12 @@
     }
 
     @Override
-    public void onColorsChanged(WallpaperColors colors, int which) {
+    public void onColorsChanged(WallpaperColors colors, int which, int userId) {
+        if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+            // Colors do not belong to current user, ignoring.
+            return;
+        }
+
         super.onColorsChanged(colors, which);
 
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 91ca571..302bc2d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -24,10 +24,12 @@
 import android.os.Handler;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -39,7 +41,7 @@
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
     public DozeMachine assembleMachine(DozeService dozeService) {
         Context context = dozeService;
-        SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
 
         DozeHost host = getHost(dozeService);
@@ -63,7 +65,7 @@
                         handler, wakeLock, machine),
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager),
                 createDozeScreenState(wrappedService),
-                createDozeScreenBrightness(context, wrappedService, sensorManager, handler),
+                createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
         });
 
         return machine;
@@ -74,10 +76,11 @@
     }
 
     private DozeMachine.Part createDozeScreenBrightness(Context context,
-            DozeMachine.Service service, SensorManager sensorManager, Handler handler) {
+            DozeMachine.Service service, SensorManager sensorManager, DozeHost host,
+            Handler handler) {
         Sensor sensor = DozeSensors.findSensorWithType(sensorManager,
                 context.getString(R.string.doze_brightness_sensor_type));
-        return new DozeScreenBrightness(context, service, sensorManager, sensor, handler);
+        return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler);
     }
 
     private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 9b97634..7db118d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -42,6 +42,7 @@
 
     void onDoubleTap(float x, float y);
 
+    default void setAodDimmingScrim(float scrimOpacity) {}
     void setDozeScreenBrightness(int value);
 
     void onIgnoreTouchWhilePulsing(boolean ignore);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 32baf94..30420529 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -32,22 +32,28 @@
 public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListener {
     private final Context mContext;
     private final DozeMachine.Service mDozeService;
+    private final DozeHost mDozeHost;
     private final Handler mHandler;
     private final SensorManager mSensorManager;
     private final Sensor mLightSensor;
     private final int[] mSensorToBrightness;
+    private final int[] mSensorToScrimOpacity;
     private boolean mRegistered;
 
     public DozeScreenBrightness(Context context, DozeMachine.Service service,
-            SensorManager sensorManager, Sensor lightSensor, Handler handler) {
+            SensorManager sensorManager, Sensor lightSensor, DozeHost host,
+            Handler handler) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
         mLightSensor = lightSensor;
+        mDozeHost = host;
         mHandler = handler;
 
         mSensorToBrightness = context.getResources().getIntArray(
                 R.array.config_doze_brightness_sensor_to_brightness);
+        mSensorToScrimOpacity = context.getResources().getIntArray(
+                R.array.config_doze_brightness_sensor_to_scrim_opacity);
     }
 
     @Override
@@ -74,13 +80,26 @@
     @Override
     public void onSensorChanged(SensorEvent event) {
         if (mRegistered) {
-            int brightness = computeBrightness((int) event.values[0]);
+            int sensorValue = (int) event.values[0];
+            int brightness = computeBrightness(sensorValue);
             if (brightness > 0) {
                 mDozeService.setDozeScreenBrightness(brightness);
             }
+
+            int scrimOpacity = computeScrimOpacity(sensorValue);
+            if (scrimOpacity >= 0) {
+                mDozeHost.setAodDimmingScrim(scrimOpacity / 255f);
+            }
         }
     }
 
+    private int computeScrimOpacity(int sensorValue) {
+        if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) {
+            return -1;
+        }
+        return mSensorToScrimOpacity[sensorValue];
+    }
+
     private int computeBrightness(int sensorValue) {
         if (sensorValue < 0 || sensorValue >= mSensorToBrightness.length) {
             return -1;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index bf1c060..566353c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -87,25 +87,29 @@
                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
                         null /* setting */,
                         dozeParameters.getPulseOnSigMotion(),
-                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */),
+                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
+                        false /* touchscreen */),
                 mPickupSensor = new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PULSE_ON_PICK_UP,
                         config.pulseOnPickupAvailable(),
-                        DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */),
+                        DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */,
+                        false /* touchscreen */),
                 new TriggerSensor(
                         findSensorWithType(config.doubleTapSensorType()),
                         Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
                         true /* configured */,
                         DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
-                        dozeParameters.doubleTapReportsTouchCoordinates()),
+                        dozeParameters.doubleTapReportsTouchCoordinates(),
+                        true /* touchscreen */),
                 new TriggerSensor(
                         findSensorWithType(config.longPressSensorType()),
                         Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
                         false /* settingDef */,
                         true /* configured */,
                         DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
-                        true /* reports touch coordinates */),
+                        true /* reports touch coordinates */,
+                        true /* touchscreen */),
         };
 
         mProxSensor = new ProxSensor();
@@ -141,6 +145,15 @@
         }
     }
 
+    /** Set the listening state of only the sensors that require the touchscreen. */
+    public void setTouchscreenSensorsListening(boolean listening) {
+        for (TriggerSensor sensor : mSensors) {
+            if (sensor.mRequiresTouchscreen) {
+                sensor.setListening(listening);
+            }
+        }
+    }
+
     public void reregisterAllSensors() {
         for (TriggerSensor s : mSensors) {
             s.setListening(false);
@@ -278,25 +291,28 @@
         final String mSetting;
         final boolean mReportsTouchCoordinates;
         final boolean mSettingDefault;
+        final boolean mRequiresTouchscreen;
 
         private boolean mRequested;
         private boolean mRegistered;
         private boolean mDisabled;
 
         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
-                boolean reportsTouchCoordinates) {
+                boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
             this(sensor, setting, true /* settingDef */, configured, pulseReason,
-                    reportsTouchCoordinates);
+                    reportsTouchCoordinates, requiresTouchscreen);
         }
 
         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
-                boolean configured, int pulseReason, boolean reportsTouchCoordinates) {
+                boolean configured, int pulseReason, boolean reportsTouchCoordinates,
+                boolean requiresTouchscreen) {
             mSensor = sensor;
             mSetting = setting;
             mSettingDefault = settingDef;
             mConfigured = configured;
             mPulseReason = pulseReason;
             mReportsTouchCoordinates = reportsTouchCoordinates;
+            mRequiresTouchscreen = requiresTouchscreen;
         }
 
         public void setListening(boolean listen) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 15981e5..4583160 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -198,6 +198,7 @@
                 mDozeSensors.setListening(false);
                 break;
             case DOZE_PULSING:
+                mDozeSensors.setTouchscreenSensorsListening(false);
                 mDozeSensors.setProxListening(true);
                 break;
             case FINISH:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 8847452..dc626fb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -95,17 +95,22 @@
                 unscheduleTimeTick();
                 break;
         }
-        mHost.setAnimateWakeup(shouldAnimateWakeup(newState));
+        updateAnimateWakeup(newState);
     }
 
-    private boolean shouldAnimateWakeup(DozeMachine.State state) {
+    private void updateAnimateWakeup(DozeMachine.State state) {
         switch (state) {
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSING:
             case DOZE_PULSE_DONE:
-                return true;
+                mHost.setAnimateWakeup(true);
+                break;
+            case FINISH:
+                // Keep current state.
+                break;
             default:
-                return false;
+                mHost.setAnimateWakeup(false);
+                break;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
index 6733421..abc5667 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
@@ -65,7 +65,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             boolean handled = true;
             try {
                 // To be implemented for input handling over Pip windows
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 2283c13..3794ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -56,6 +56,8 @@
     private boolean mWakeAndUnlocking;
     private boolean mFullyPulsing;
 
+    private float mAodFrontScrimOpacity = 0;
+
     public DozeScrimController(ScrimController scrimController, Context context) {
         mContext = context;
         mScrimController = scrimController;
@@ -70,7 +72,8 @@
             mDozingAborted = false;
             abortAnimations();
             mScrimController.setDozeBehindAlpha(1f);
-            mScrimController.setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? 0f : 1f);
+            mScrimController.setDozeInFrontAlpha(
+                    mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
         } else {
             cancelPulsing();
             if (animate) {
@@ -88,6 +91,19 @@
         }
     }
 
+    /**
+     * Set the opacity of the front scrim when showing AOD1
+     *
+     * Used to emulate lower brightness values than the hardware supports natively.
+     */
+    public void setAodDimmingScrim(float scrimOpacity) {
+        mAodFrontScrimOpacity = scrimOpacity;
+        if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
+                && mDozeParameters.getAlwaysOn()) {
+            mScrimController.setDozeInFrontAlpha(mAodFrontScrimOpacity);
+        }
+    }
+
     public void setWakeAndUnlocking() {
         // Immediately abort the doze scrims in case of wake-and-unlock
         // for pulsing so the Keyguard fade-out animation scrim can take over.
@@ -126,7 +142,8 @@
         if (mDozing && !mWakeAndUnlocking) {
             mScrimController.setDozeBehindAlpha(1f);
             mScrimController.setDozeInFrontAlpha(
-                    mDozeParameters.getAlwaysOn() && !mDozingAborted ? 0f : 1f);
+                    mDozeParameters.getAlwaysOn() && !mDozingAborted ?
+                            mAodFrontScrimOpacity : 1f);
         }
     }
 
@@ -337,7 +354,7 @@
             // Signal that the pulse is all finished so we can turn the screen off now.
             pulseFinished();
             if (mDozeParameters.getAlwaysOn()) {
-                mScrimController.setDozeInFrontAlpha(0);
+                mScrimController.setDozeInFrontAlpha(mAodFrontScrimOpacity);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 02202cf..cb96dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -29,6 +29,7 @@
 import com.android.keyguard.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 /**
@@ -101,6 +102,7 @@
     private final Context mContext;
     private int mPendingAuthenticatedUserId = -1;
     private boolean mPendingShowBouncer;
+    private boolean mHasScreenTurnedOnSinceAuthenticating;
 
     public FingerprintUnlockController(Context context,
             DozeScrimController dozeScrimController,
@@ -113,6 +115,7 @@
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
         mUpdateMonitor.registerCallback(this);
         Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
+        Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
         mDozeScrimController = dozeScrimController;
         mKeyguardViewMediator = keyguardViewMediator;
@@ -184,8 +187,13 @@
             Trace.endSection();
             return;
         }
+        startWakeAndUnlock(calculateMode());
+    }
+
+    public void startWakeAndUnlock(int mode) {
         boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
-        mMode = calculateMode();
+        mMode = mode;
+        mHasScreenTurnedOnSinceAuthenticating = false;
         if (mMode == MODE_WAKE_AND_UNLOCK_PULSING && pulsingOrAod()) {
             // If we are waking the device up while we are pulsing the clock and the
             // notifications would light up first, creating an unpleasant animation.
@@ -228,6 +236,7 @@
                             true /* allowEnterAnimation */);
                 } else {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
+
                     mDozeScrimController.abortDoze();
                 }
                 mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -354,4 +363,16 @@
             }
         }
     };
+
+    private final ScreenLifecycle.Observer mScreenObserver =
+            new ScreenLifecycle.Observer() {
+                @Override
+                public void onScreenTurnedOn() {
+                    mHasScreenTurnedOnSinceAuthenticating = true;
+                }
+            };
+
+    public boolean hasScreenTurnedOnSinceAuthenticating() {
+        return mHasScreenTurnedOnSinceAuthenticating;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index c487901..87f5ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -158,7 +158,7 @@
     }
 
     @Override
-    public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+    public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
 
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 8bc6563..004a604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -15,7 +15,9 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
@@ -42,8 +44,13 @@
     private View mTouchingChild;
 
     public NearestTouchFrame(Context context, AttributeSet attrs) {
+        this(context, attrs, context.getResources().getConfiguration());
+    }
+
+    @VisibleForTesting
+    NearestTouchFrame(Context context, AttributeSet attrs, Configuration c) {
         super(context, attrs);
-        mIsActive = context.getResources().getConfiguration().smallestScreenWidthDp < 600;
+        mIsActive = c.smallestScreenWidthDp < 600;
     }
 
     @Override
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 ab021b4..5d8d01c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -142,6 +142,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.graphics.ColorUtils;
 import com.android.internal.logging.MetricsLogger;
@@ -749,7 +750,7 @@
     private SysuiColorExtractor mColorExtractor;
     private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
+    @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
         final int N = array.size();
@@ -3787,6 +3788,15 @@
 
     private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
+        if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
+                && mUnlockMethodCache.canSkipBouncer()
+                && !mLeaveOpenOnKeyguardHide
+                && isPulsing()) {
+            // Reuse the fingerprint wake-and-unlock transition if we dismiss keyguard from a pulse.
+            // TODO: Factor this transition out of FingerprintUnlockController.
+            mFingerprintUnlockController.startWakeAndUnlock(
+                    FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
+        }
         if (mStatusBarKeyguardViewManager.isShowing()) {
             mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
                     afterKeyguardGone);
@@ -4223,7 +4233,10 @@
 
     public void showKeyguard() {
         mKeyguardRequested = true;
+        mLeaveOpenOnKeyguardHide = false;
+        mPendingRemoteInputView = null;
         updateIsKeyguard();
+        mAssistManager.onLockscreenShown();
     }
 
     public boolean hideKeyguard() {
@@ -4272,14 +4285,11 @@
         }
         updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
         updatePanelExpansionForKeyguard();
-        mLeaveOpenOnKeyguardHide = false;
         if (mDraggedDownRow != null) {
             mDraggedDownRow.setUserLocked(false);
             mDraggedDownRow.notifyHeightChanged(false  /* needsAnimation */);
             mDraggedDownRow = null;
         }
-        mPendingRemoteInputView = null;
-        mAssistManager.onLockscreenShown();
     }
 
     private void updatePanelExpansionForKeyguard() {
@@ -4419,15 +4429,19 @@
         setBarState(StatusBarState.SHADE);
         View viewToClick = null;
         if (mLeaveOpenOnKeyguardHide) {
-            mLeaveOpenOnKeyguardHide = false;
+            if (!mKeyguardRequested) {
+                mLeaveOpenOnKeyguardHide = false;
+            }
             long delay = calculateGoingToFullShadeDelay();
             mNotificationPanel.animateToFullShade(delay);
             if (mDraggedDownRow != null) {
                 mDraggedDownRow.setUserLocked(false);
                 mDraggedDownRow = null;
             }
-            viewToClick = mPendingRemoteInputView;
-            mPendingRemoteInputView = null;
+            if (!mKeyguardRequested) {
+                viewToClick = mPendingRemoteInputView;
+                mPendingRemoteInputView = null;
+            }
 
             // Disable layout transitions in navbar for this transition because the load is just
             // too heavy for the CPU and GPU on any device.
@@ -5566,6 +5580,11 @@
             mStatusBarWindowManager.setDozeScreenBrightness(value);
         }
 
+        @Override
+        public void setAodDimmingScrim(float scrimOpacity) {
+            mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+        }
+
         public void dispatchDoubleTap(float viewX, float viewY) {
             dispatchTap(mAmbientIndicationContainer, viewX, viewY);
             dispatchTap(mAmbientIndicationContainer, viewX, viewY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e142d62..3147009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -71,6 +71,7 @@
 
     protected final Context mContext;
     private final StatusBarWindowManager mStatusBarWindowManager;
+    private final boolean mDisplayBlanksAfterDoze;
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
@@ -121,6 +122,8 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
+        mDisplayBlanksAfterDoze = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_displayBlanksAfterDoze);
     }
 
     public void registerStatusBar(StatusBar statusBar,
@@ -153,7 +156,6 @@
      */
     protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
         if (mBouncer.needsFullscreenBouncer() && !mDozing) {
-
             // The keyguard might be showing (already). So we need to hide it.
             mStatusBar.hideKeyguard();
             mBouncer.show(true /* resetSecuritySelection */);
@@ -164,6 +166,7 @@
                 mBouncer.prepare();
             }
         }
+        updateStates();
     }
 
     private void showBouncer() {
@@ -247,7 +250,9 @@
     public void setDozing(boolean dozing) {
         if (mDozing != dozing) {
             mDozing = dozing;
-            reset(dozing /* hideBouncerWhenShowing */);
+            if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+                reset(dozing /* hideBouncerWhenShowing */);
+            }
             updateStates();
         }
     }
@@ -373,7 +378,11 @@
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
                     if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
-                        if (!mScreenTurnedOn) {
+                        boolean turnedOnSinceAuth =
+                                mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
+                        if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
+                            // Not ready to animate yet; either because the screen is not on yet,
+                            // or it is on but will turn off before waking out of doze.
                             mDeferScrimFadeOut = true;
                         } else {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index f4197a3..c060b08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -249,14 +249,28 @@
         state.paddingMap.clear();
         int notGoneIndex = 0;
         ExpandableView lastView = null;
+        int firstHiddenIndex = ambientState.isDark()
+                ? (ambientState.hasPulsingNotifications() ? 1 : 0)
+                : childCount;
+
+        // The goal here is to fill the padding map, by iterating over how much padding each child
+        // needs. The map is thereby reused, by first filling it with the padding amount and when
+        // iterating over it again, it's filled with the actual resolved value.
+
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
                 if (v == ambientState.getShelf()) {
                     continue;
                 }
+                if (i >= firstHiddenIndex) {
+                    // we need normal padding now, to be in sync with what the stack calculates
+                    lastView = null;
+                    ExpandableViewState viewState = resultState.getViewStateForView(v);
+                    viewState.hidden = true;
+                }
                 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
-                float increasedPadding = v.getIncreasedPaddingAmount();;
+                float increasedPadding = v.getIncreasedPaddingAmount();
                 if (increasedPadding != 0.0f) {
                     state.paddingMap.put(v, increasedPadding);
                     if (lastView != null) {
@@ -279,6 +293,8 @@
                         state.paddingMap.put(lastView, newValue);
                     }
                 } else if (lastView != null) {
+
+                    // Let's now resolve the value to an actual padding
                     float newValue = getPaddingForValue(state.paddingMap.get(lastView));
                     state.paddingMap.put(lastView, newValue);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
new file mode 100644
index 0000000..5790ba3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEventListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Wrapper around sensor manager that hides potential sources of latency.
+ *
+ * Offloads fetching (non-dynamic) sensors and (un)registering listeners onto a background thread
+ * without blocking. Note that this means registering listeners now always appears successful even
+ * if it is not.
+ */
+public class AsyncSensorManager extends SensorManager {
+
+    private static final String TAG = "AsyncSensorManager";
+
+    private final SensorManager mInner;
+    private final List<Sensor> mSensorCache;
+    private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
+    @VisibleForTesting final Handler mHandler;
+
+    public AsyncSensorManager(SensorManager inner) {
+        mInner = inner;
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
+    }
+
+    @Override
+    protected List<Sensor> getFullSensorList() {
+        return mSensorCache;
+    }
+
+    @Override
+    protected List<Sensor> getFullDynamicSensorList() {
+        return mInner.getDynamicSensorList(Sensor.TYPE_ALL);
+    }
+
+    @Override
+    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs,
+            Handler handler, int maxReportLatencyUs, int reservedFlags) {
+        mHandler.post(() -> {
+            if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
+                Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean flushImpl(SensorEventListener listener) {
+        return mInner.flush(listener);
+    }
+
+    @Override
+    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
+            HardwareBuffer hardwareBuffer) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
+            Handler handler) {
+        mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler));
+    }
+
+    @Override
+    protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) {
+        mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback));
+    }
+
+    @Override
+    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        mHandler.post(() -> {
+            if (!mInner.requestTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+            boolean disable) {
+        Preconditions.checkArgument(disable);
+
+        mHandler.post(() -> {
+            if (!mInner.cancelTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean initDataInjectionImpl(boolean enable) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
+            long timestamp) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+        mHandler.post(() -> mInner.setOperationParameter(parameter));
+        return true;
+    }
+
+    @Override
+    protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+        mHandler.post(() -> {
+            if (sensor == null) {
+                mInner.unregisterListener(listener);
+            } else {
+                mInner.unregisterListener(listener, sensor);
+            }
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index ad47411..b08b26d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -100,7 +100,7 @@
     private final MediaSessions mMediaSessions;
     protected C mCallbacks = new C();
     private final State mState = new State();
-    private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+    protected final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
     private final Vibrator mVibrator;
     private final boolean mHasVibrator;
     private boolean mShowA11yStream;
@@ -906,7 +906,7 @@
         }
     }
 
-    private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
+    protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
         private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
 
         private int mNextStream = DYNAMIC_STREAM_START_INDEX;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index fe3221a..c275806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -51,14 +51,16 @@
     DozeScreenBrightness mScreen;
     FakeSensorManager.FakeGenericSensor mSensor;
     FakeSensorManager mSensorManager;
+    DozeHostFake mHostFake;
 
     @Before
     public void setUp() throws Exception {
         mServiceFake = new DozeServiceFake();
+        mHostFake = new DozeHostFake();
         mSensorManager = new FakeSensorManager(mContext);
         mSensor = mSensorManager.getFakeLightSensor();
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                mSensor.getSensor(), null /* handler */);
+                mSensor.getSensor(), mHostFake, null /* handler */);
     }
 
     @Test
@@ -133,7 +135,7 @@
     @Test
     public void testNullSensor() throws Exception {
         mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                null /* sensor */, null /* handler */);
+                null /* sensor */, mHostFake, null /* handler */);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 577dc52..ed1491d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -18,10 +18,12 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -43,7 +45,29 @@
 
     @Before
     public void setup() {
-        mNearestTouchFrame = new NearestTouchFrame(mContext, null);
+        Configuration c = new Configuration(mContext.getResources().getConfiguration());
+        c.smallestScreenWidthDp = 500;
+        mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);
+    }
+
+    @Test
+    public void testNoActionOnLargeDevices() {
+        Configuration c = new Configuration(mContext.getResources().getConfiguration());
+        c.smallestScreenWidthDp = 700;
+        mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);
+
+        View left = mockViewAt(0, 0, 10, 10);
+        View right = mockViewAt(20, 0, 10, 10);
+
+        mNearestTouchFrame.addView(left);
+        mNearestTouchFrame.addView(right);
+        mNearestTouchFrame.onMeasure(0, 0);
+
+        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
+                12 /* x */, 5 /* y */, 0);
+        mNearestTouchFrame.onTouchEvent(ev);
+        verify(left, never()).onTouchEvent(eq(ev));
+        ev.recycle();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index c33897e..a706368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -65,6 +65,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.CommandQueue;
@@ -500,6 +501,14 @@
             mSystemServicesProxy = ssp;
             mNotificationPanel = panelView;
             mBarService = barService;
+            mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+        }
+
+        private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
+            WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle();
+            wakefulnessLifecycle.dispatchStartedWakingUp();
+            wakefulnessLifecycle.dispatchFinishedWakingUp();
+            return wakefulnessLifecycle;
         }
 
         public void setBarStateForTest(int state) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
new file mode 100644
index 0000000..469bdc0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.hardware.FakeSensorManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AsyncSensorManagerTest extends SysuiTestCase {
+
+    private TestableAsyncSensorManager mAsyncSensorManager;
+    private FakeSensorManager mFakeSensorManager;
+    private SensorEventListener mListener;
+    private FakeSensorManager.MockProximitySensor mSensor;
+
+    @Before
+    public void setUp() throws Exception {
+        mFakeSensorManager = new FakeSensorManager(mContext);
+        mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager);
+        mSensor = mFakeSensorManager.getMockProximitySensor();
+        mListener = mock(SensorEventListener.class);
+    }
+
+    @Test
+    public void registerListenerImpl() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+
+        mAsyncSensorManager.waitUntilRequestsCompleted();
+
+        // Verify listener was registered.
+        mSensor.sendProximityResult(true);
+        verify(mListener).onSensorChanged(any());
+    }
+
+    @Test
+    public void unregisterListenerImpl_withNullSensor() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+        mAsyncSensorManager.unregisterListener(mListener);
+
+        mAsyncSensorManager.waitUntilRequestsCompleted();
+
+        // Verify listener was unregistered.
+        mSensor.sendProximityResult(true);
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void unregisterListenerImpl_withSensor() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+        mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
+
+        mAsyncSensorManager.waitUntilRequestsCompleted();
+
+        // Verify listener was unregistered.
+        mSensor.sendProximityResult(true);
+        verifyNoMoreInteractions(mListener);
+    }
+
+    private class TestableAsyncSensorManager extends AsyncSensorManager {
+        public TestableAsyncSensorManager(SensorManager sensorManager) {
+            super(sensorManager);
+        }
+
+        public void waitUntilRequestsCompleted() {
+            assertTrue(mHandler.runWithScissors(() -> {}, 0));
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 12e9f7c..06568f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.media.AudioManager;
+import android.media.session.MediaSession;
 import android.support.test.filters.SmallTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -73,6 +74,18 @@
         verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
     }
 
+    @Test
+    public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
+        MediaSession.Token token = new MediaSession.Token(null);
+        mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+    }
+
+    @Test
+    public void testOnRemoteRemove_newStream_noNullPointer() {
+        MediaSession.Token token = new MediaSession.Token(null);
+        mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+    }
+
     static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
         public TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s) {
             super(context);
diff --git a/services/Android.mk b/services/Android.mk
index 0986e0a..ed2ba1f8 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -40,8 +40,8 @@
 
 # The convention is to name each service module 'services.$(module_name)'
 LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services)) \
-    android.hidl.base-V1.0-java-static \
-    android.hardware.biometrics.fingerprint-V2.1-java-static
+    android.hidl.base-V1.0-java \
+    android.hardware.biometrics.fingerprint-V2.1-java
 
 LOCAL_JAVA_LIBRARIES := \
     android.hidl.manager-V1.0-java
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 94004ce..5e188e0 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -27,6 +27,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     time_zone_distro \
     time_zone_distro_installer \
+    android.hidl.base-V1.0-java \
     android.hardware.weaver-V1.0-java \
     android.hardware.biometrics.fingerprint-V2.1-java \
     android.hardware.oemlock-V1.0-java \
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index fdc0bba..d60df83 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -27,6 +27,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
+import android.location.LocationManager;
 import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.INetworkScoreService;
@@ -113,6 +114,16 @@
         }
     };
 
+    private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
+                refreshBinding();
+            }
+        }
+    };
+
     /**
      * Clears scores when the active scorer package is no longer valid and
      * manages the service connection.
@@ -241,6 +252,10 @@
                 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
                 null /* scheduler */);
         mHandler = new ServiceHandler(looper);
+        IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
+        mContext.registerReceiverAsUser(
+                mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
+                null /* broadcastPermission*/, mHandler);
         mContentObserver = new DispatchingContentObserver(context, mHandler);
         mServiceConnProducer = serviceConnProducer;
     }
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index 42777bf..bfd4247 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest.permission;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -79,7 +80,7 @@
         List<NetworkScorerAppData> appDataList = new ArrayList<>();
         for (int i = 0; i < resolveInfos.size(); i++) {
             final ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
-            if (hasPermissions(serviceInfo.packageName)) {
+            if (hasPermissions(serviceInfo.applicationInfo.uid, serviceInfo.packageName)) {
                 if (VERBOSE) {
                     Log.v(TAG, serviceInfo.packageName + " is a valid scorer/recommender.");
                 }
@@ -197,12 +198,33 @@
         return null;
     }
 
-    private boolean hasPermissions(String packageName) {
+    private boolean hasPermissions(final int uid, final String packageName) {
+        return hasScoreNetworksPermission(packageName)
+                && canAccessLocation(uid, packageName);
+    }
+
+    private boolean hasScoreNetworksPermission(String packageName) {
         final PackageManager pm = mContext.getPackageManager();
         return pm.checkPermission(permission.SCORE_NETWORKS, packageName)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean canAccessLocation(int uid, String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        final AppOpsManager appOpsManager =
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        return isLocationModeEnabled()
+                && pm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName)
+                == PackageManager.PERMISSION_GRANTED
+                && appOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName)
+                == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private boolean isLocationModeEnabled() {
+        return mSettingsFacade.getSecureInt(mContext, Settings.Secure.LOCATION_MODE,
+                Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF;
+    }
+
     /**
      * Set the specified package as the default scorer application.
      *
@@ -270,23 +292,20 @@
             return;
         }
 
-        // the active scorer isn't valid, revert to the default if it's different
+        int newEnabledSetting = NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF;
+        // the active scorer isn't valid, revert to the default if it's different and valid
         final String defaultPackageName = getDefaultPackageSetting();
-        if (!TextUtils.equals(currentPackageName, defaultPackageName)) {
-            setNetworkRecommendationsPackage(defaultPackageName);
+        if (!TextUtils.equals(currentPackageName, defaultPackageName)
+                && getScorer(defaultPackageName) != null) {
             if (DEBUG) {
-                Log.d(TAG, "Defaulted the network recommendations app to: " + defaultPackageName);
+                Log.d(TAG, "Defaulting the network recommendations app to: "
+                        + defaultPackageName);
             }
-            if (getScorer(defaultPackageName) != null) { // the default is valid
-                if (DEBUG) Log.d(TAG, defaultPackageName + " is now the active scorer.");
-                setNetworkRecommendationsEnabledSetting(
-                        NetworkScoreManager.RECOMMENDATIONS_ENABLED_ON);
-            } else { // the default isn't valid either, we're disabled at this point
-                if (DEBUG) Log.d(TAG, defaultPackageName + " is not an active scorer.");
-                setNetworkRecommendationsEnabledSetting(
-                        NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
-            }
+            setNetworkRecommendationsPackage(defaultPackageName);
+            newEnabledSetting = NetworkScoreManager.RECOMMENDATIONS_ENABLED_ON;
         }
+
+        setNetworkRecommendationsEnabledSetting(newEnabledSetting);
     }
 
     /**
@@ -352,6 +371,9 @@
     private void setNetworkRecommendationsPackage(String packageName) {
         mSettingsFacade.putString(mContext,
                 Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, packageName);
+        if (VERBOSE) {
+            Log.d(TAG, Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE + " set to " + packageName);
+        }
     }
 
     private int getNetworkRecommendationsEnabledSetting() {
@@ -361,6 +383,9 @@
     private void setNetworkRecommendationsEnabledSetting(int value) {
         mSettingsFacade.putInt(mContext,
                 Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, value);
+        if (VERBOSE) {
+            Log.d(TAG, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED + " set to " + value);
+        }
     }
 
     /**
@@ -382,5 +407,9 @@
         public int getInt(Context context, String name, int defaultValue) {
             return Settings.Global.getInt(context.getContentResolver(), name, defaultValue);
         }
+
+        public int getSecureInt(Context context, String name, int defaultValue) {
+            return Settings.Secure.getInt(context.getContentResolver(), name, defaultValue);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ddc524f..54ea2c7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -714,7 +714,9 @@
 
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
-                && !mKeyguardController.isKeyguardShowing();
+                && !mKeyguardController.isKeyguardShowing()
+                && !(UserManager.isDeviceInDemoMode(mContext)
+                        && mUserController.getCurrentUser().isDemo());
     }
 
     private static ThreadPriorityBooster sThreadPriorityBooster = new ThreadPriorityBooster(
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2aa5350..7a19cc3 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2161,7 +2161,7 @@
         if (mStartingWindowState == STARTING_WINDOW_SHOWN && behindFullscreenActivity) {
             if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
             mStartingWindowState = STARTING_WINDOW_REMOVED;
-            mWindowContainerController.removeHiddenStartingWindow();
+            mWindowContainerController.removeStartingWindow();
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9925ba0..79448ca 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -650,6 +650,13 @@
         return topRunningActivityLocked(false /* focusableOnly */);
     }
 
+    void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
+        outActivities.clear();
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            mTaskHistory.get(taskNdx).getAllRunningVisibleActivitiesLocked(outActivities);
+        }
+    }
+
     private ActivityRecord topRunningActivityLocked(boolean focusableOnly) {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5f42cdb..e8bc68f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -441,6 +441,8 @@
 
     final ActivityMetricsLogger mActivityMetricsLogger;
 
+    private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
+
     @Override
     protected int getChildCount() {
         return mActivityDisplays.size();
@@ -954,17 +956,21 @@
                 if (!isFocusedStack(stack)) {
                     continue;
                 }
-                ActivityRecord hr = stack.topRunningActivityLocked();
-                if (hr != null) {
-                    if (hr.app == null && app.uid == hr.info.applicationInfo.uid
-                            && processName.equals(hr.processName)) {
+                stack.getAllRunningVisibleActivitiesLocked(mTmpActivityList);
+                final ActivityRecord top = stack.topRunningActivityLocked();
+                final int size = mTmpActivityList.size();
+                for (int i = 0; i < size; i++) {
+                    final ActivityRecord activity = mTmpActivityList.get(i);
+                    if (activity.app == null && app.uid == activity.info.applicationInfo.uid
+                            && processName.equals(activity.processName)) {
                         try {
-                            if (realStartActivityLocked(hr, app, true, true)) {
+                            if (realStartActivityLocked(activity, app,
+                                    top == activity /* andResume */, true /* checkConfig */)) {
                                 didSomething = true;
                             }
                         } catch (RemoteException e) {
                             Slog.w(TAG, "Exception in new application when starting activity "
-                                  + hr.intent.getComponent().flattenToShortString(), e);
+                                    + top.intent.getComponent().flattenToShortString(), e);
                             throw e;
                         }
                     }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5753fbc..be0a1d9 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1159,6 +1159,17 @@
         return null;
     }
 
+    void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
+        if (mStack != null) {
+            for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = mActivities.get(activityNdx);
+                if (!r.finishing && r.okToShowLocked() && r.visible) {
+                    outActivities.add(r);
+                }
+            }
+        }
+    }
+
     ActivityRecord topRunningActivityWithStartingWindowLocked() {
         if (mStack != null) {
             for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 89deb49..95d7e9f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -669,12 +669,6 @@
         }
 
         if (stopped) {
-            // Evict the user's credential encryption key
-            try {
-                getStorageManager().lockUserKey(userId);
-            } catch (RemoteException re) {
-                throw re.rethrowAsRuntimeException();
-            }
             mInjector.systemServiceManagerCleanupUser(userId);
             synchronized (mLock) {
                 mInjector.getActivityStackSupervisor().removeUserLocked(userId);
@@ -683,6 +677,12 @@
             if (getUserInfo(userId).isEphemeral()) {
                 mInjector.getUserManager().removeUser(userId);
             }
+            // Evict the user's credential encryption key.
+            try {
+                getStorageManager().lockUserKey(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowAsRuntimeException();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/TunerCallback.java
index 25f3775..331b657 100644
--- a/services/core/java/com/android/server/broadcastradio/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/TunerCallback.java
@@ -86,8 +86,8 @@
     }
 
     @Override
-    public void onProgramInfoChanged() {
-        dispatch(() -> mClientCallback.onProgramInfoChanged());
+    public void onCurrentProgramInfoChanged() {
+        dispatch(() -> mClientCallback.onCurrentProgramInfoChanged());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 207bdba..f7efff0f 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -186,7 +186,12 @@
     private static String hintsToString(int hints) {
         switch (hints) {
             case 0 : return "none";
-            case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS : return "disable_effects";
+            case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS:
+                    return "disable_effects";
+            case NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS:
+                    return "disable_call_effects";
+            case NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS:
+                    return "disable_notification_effects";
             default: return Integer.toString(hints);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9d6748c..33615ba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10004,6 +10004,7 @@
     public void shutdown() {
         mPackageUsage.writeNow(mPackages);
         mCompilerStats.writeNow();
+        mDexManager.savePackageDexUsageNow();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 79e02b5..947e01c4 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -592,6 +592,13 @@
         return existingValue == null ? newValue : existingValue;
     }
 
+    /**
+     * Saves the in-memory package dex usage to disk right away.
+     */
+    public void savePackageDexUsageNow() {
+        mPackageDexUsage.writeNow();
+    }
+
     public static class RegisterDexModuleResult {
         public RegisterDexModuleResult() {
             this(false, null);
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 8819aa6..6ee26d3 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -198,8 +198,12 @@
      * Convenience method for async writes which does not force the user to pass a useless
      * (Void) null.
      */
-    public void maybeWriteAsync() {
-      maybeWriteAsync((Void) null);
+    /*package*/ void maybeWriteAsync() {
+      maybeWriteAsync(null);
+    }
+
+    /*package*/ void writeNow() {
+        writeInternal(null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e3cf459..6afd69d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4184,7 +4184,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             boolean handled = false;
             try {
                 if (event instanceof MotionEvent
@@ -7049,6 +7049,12 @@
                 // Ignore sensor when demo rotation lock is enabled.
                 // Note that the dock orientation and HDMI rotation lock override this.
                 preferredRotation = mDemoRotation;
+            } else if (mPersistentVrModeEnabled) {
+                // While in VR, apps always prefer a portrait rotation. This does not change
+                // any apps that explicitly set landscape, but does cause sensors be ignored,
+                // and ignored any orientation lock that the user has set (this conditional
+                // should remain above the ORIENTATION_LOCKED conditional below).
+                preferredRotation = mPortraitRotation;
             } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
                 // Application just wants to remain locked in the last rotation.
                 preferredRotation = lastRotation;
@@ -7079,13 +7085,7 @@
                         || mAllowAllRotations == 1
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                         || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
-                    // In VrMode, we report the sensor as always being in default orientation so:
-                    // 1) The orientation doesn't change as the user moves their head.
-                    // 2) 2D apps within VR show in the device's default orientation.
-                    // This only overwrites the sensor-provided orientation and does not affect any
-                    // explicit orientation preferences specified by any activities.
-                    preferredRotation =
-                            mPersistentVrModeEnabled ? Surface.ROTATION_0 : sensorRotation;
+                    preferredRotation = sensorRotation;
                 } else {
                     preferredRotation = lastRotation;
                 }
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index e894275..92f1c83 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -57,6 +57,7 @@
 import android.widget.TextView;
 
 import com.android.internal.telephony.ITelephony;
+import com.android.server.RescueParty;
 import com.android.server.pm.PackageManagerService;
 
 import java.io.File;
@@ -293,11 +294,20 @@
                             com.android.internal.R.string.reboot_to_update_reboot));
             }
         } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
-            // Factory reset path. Set the dialog message accordingly.
-            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
-            pd.setMessage(context.getText(
-                        com.android.internal.R.string.reboot_to_reset_message));
-            pd.setIndeterminate(true);
+            if (RescueParty.isAttemptingFactoryReset()) {
+                // We're not actually doing a factory reset yet; we're rebooting
+                // to ask the user if they'd like to reset, so give them a less
+                // scary dialog message.
+                pd.setTitle(context.getText(com.android.internal.R.string.power_off));
+                pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
+                pd.setIndeterminate(true);
+            } else {
+                // Factory reset path. Set the dialog message accordingly.
+                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
+                pd.setMessage(context.getText(
+                            com.android.internal.R.string.reboot_to_reset_message));
+                pd.setIndeterminate(true);
+            }
         } else if (mReason != null && mReason.equals(PowerManager.SHUTDOWN_USER_REQUESTED)) {
             Dialog d = new Dialog(context);
             d.setContentView(com.android.internal.R.layout.shutdown_dialog);
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 88b6d87..a35383f 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -29,6 +29,7 @@
 import android.os.Environment;
 import android.os.FileObserver;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Message;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -154,20 +155,8 @@
 
     private static final String TV_NOTIFICATION_CHANNEL_ID = "devicestoragemonitor.tv";
 
-    /**
-     * Handler that checks the amount of disk space on the device and sends a
-     * notification if the device runs low on disk space
-     */
-    private final Handler mHandler = new Handler(IoThread.get().getLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_CHECK:
-                    check();
-                    return;
-            }
-        }
-    };
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
 
     private State findOrCreateState(UUID uuid) {
         State state = mStates.get(uuid);
@@ -256,6 +245,20 @@
 
     public DeviceStorageMonitorService(Context context) {
         super(context);
+
+        mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+
+        mHandler = new Handler(mHandlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_CHECK:
+                        check();
+                        return;
+                }
+            }
+        };
     }
 
     private static boolean isBootImageOnDisk() {
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 69d8ca6..8f50a39 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -24,6 +24,7 @@
 import android.service.vr.IVrManager;
 import android.util.Log;
 import android.view.Surface;
+import android.view.WindowManagerInternal;
 
 import com.android.server.vr.VrManagerService;
 
@@ -74,6 +75,7 @@
     public static final int MIN_VR_DISPLAY_DPI = 1;
 
     private final ActivityManagerInternal mActivityManagerInternal;
+    private final WindowManagerInternal mWindowManagerInternal;
     private final DisplayManager mDisplayManager;
     private final IVrManager mVrManager;
     private final Object mVdLock = new Object();
@@ -103,9 +105,11 @@
     private boolean mBootsToVr = false;  // The device boots into VR (standalone VR device)
 
     public Vr2dDisplay(DisplayManager displayManager,
-           ActivityManagerInternal activityManagerInternal, IVrManager vrManager) {
+           ActivityManagerInternal activityManagerInternal,
+           WindowManagerInternal windowManagerInternal, IVrManager vrManager) {
         mDisplayManager = displayManager;
         mActivityManagerInternal = activityManagerInternal;
+        mWindowManagerInternal = windowManagerInternal;
         mVrManager = vrManager;
         mVirtualDisplayWidth = DEFAULT_VIRTUAL_DISPLAY_WIDTH;
         mVirtualDisplayHeight = DEFAULT_VIRTUAL_DISPLAY_HEIGHT;
@@ -296,13 +300,12 @@
                     UNIQUE_DISPLAY_ID);
 
             if (mVirtualDisplay != null) {
-                mActivityManagerInternal.setVr2dDisplayId(
-                    mVirtualDisplay.getDisplay().getDisplayId());
+                updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
                 // Now create the ImageReader to supply a Surface to the new virtual display.
                 startImageReader();
             } else {
                 Log.w(TAG, "Virtual display id is null after createVirtualDisplay");
-                mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY);
+                updateDisplayId(INVALID_DISPLAY);
                 return;
             }
         }
@@ -310,6 +313,11 @@
         Log.i(TAG, "VD created: " + mVirtualDisplay);
     }
 
+    private void updateDisplayId(int displayId) {
+        mActivityManagerInternal.setVr2dDisplayId(displayId);
+        mWindowManagerInternal.setVr2dDisplayId(displayId);
+    }
+
     /**
      * Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
      * The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
@@ -325,7 +333,7 @@
                     } else {
                         Log.i(TAG, "Stopping Virtual Display");
                         synchronized (mVdLock) {
-                            mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY);
+                            updateDisplayId(INVALID_DISPLAY);
                             setSurfaceLocked(null); // clean up and release the surface first.
                             if (mVirtualDisplay != null) {
                                 mVirtualDisplay.release();
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 425b23f..b6b964b 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -58,6 +58,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.WindowManagerInternal;
 
 import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
@@ -633,8 +634,11 @@
 
             DisplayManager dm =
                     (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
-            ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-            mVr2dDisplay = new Vr2dDisplay(dm, ami, mVrManager);
+            mVr2dDisplay = new Vr2dDisplay(
+                    dm,
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    LocalServices.getService(WindowManagerInternal.class),
+                    mVrManager);
             mVr2dDisplay.init(getContext(), mBootsToVr);
 
             IntentFilter intentFilter = new IntentFilter();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 802054e..46097ed 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -25,6 +25,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -63,6 +64,7 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -331,11 +333,18 @@
         }
     }
 
-    private void notifyWallpaperColorsChanged(WallpaperData wallpaper, int which) {
+    private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
         boolean needsExtraction;
         synchronized (mLock) {
-            if (mColorsChangedListeners.getRegisteredCallbackCount() == 0)
+            final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
+                    mColorsChangedListeners.get(wallpaper.userId);
+            final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
+                    mColorsChangedListeners.get(UserHandle.USER_ALL);
+            // No-op until someone is listening to it.
+            if (emptyCallbackList(currentUserColorListeners)  &&
+                    emptyCallbackList(userAllColorListeners)) {
                 return;
+            }
 
             if (DEBUG) {
                 Slog.v(TAG, "notifyWallpaperColorsChanged " + which);
@@ -346,40 +355,66 @@
 
         // Let's notify the current values, it's fine if it's null, it just means
         // that we don't know yet.
-        notifyColorListeners(wallpaper.primaryColors, which);
+        notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
 
         if (needsExtraction) {
             extractColors(wallpaper);
-            notifyColorListeners(wallpaper.primaryColors, which);
+            synchronized (mLock) {
+                // Don't need to notify if nothing changed.
+                if (wallpaper.primaryColors == null) {
+                    return;
+                }
+            }
+            notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
         }
     }
 
-    private void notifyColorListeners(WallpaperColors wallpaperColors, int which) {
-        final IWallpaperManagerCallback[] listeners;
+    private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) {
+        return (list == null || list.getRegisteredCallbackCount() == 0);
+    }
+
+    private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
+            int userId) {
         final IWallpaperManagerCallback keyguardListener;
+        final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners;
+        final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners;
         synchronized (mLock) {
-            // Make a synchronized copy of the listeners to avoid concurrent list modification.
-            int callbackCount = mColorsChangedListeners.beginBroadcast();
-            listeners = new IWallpaperManagerCallback[callbackCount];
-            for (int i = 0; i < callbackCount; i++) {
-                listeners[i] = mColorsChangedListeners.getBroadcastItem(i);
-            }
-            mColorsChangedListeners.finishBroadcast();
+            currentUserColorListeners = mColorsChangedListeners.get(userId);
+            userAllColorListeners = mColorsChangedListeners.get(UserHandle.USER_ALL);
             keyguardListener = mKeyguardListener;
         }
 
-        for (int i = 0; i < listeners.length; i++) {
-            try {
-                listeners[i].onWallpaperColorsChanged(wallpaperColors, which);
-            } catch (RemoteException e) {
-                // Callback is gone, it's not necessary to unregister it since
-                // RemoteCallbackList#getBroadcastItem will take care of it.
+        if (currentUserColorListeners != null) {
+            int count = currentUserColorListeners.beginBroadcast();
+            for (int i = 0; i < count; i++) {
+                try {
+                    currentUserColorListeners.getBroadcastItem(i)
+                            .onWallpaperColorsChanged(wallpaperColors, which, userId);
+                } catch (RemoteException e) {
+                    // Callback is gone, it's not necessary to unregister it since
+                    // RemoteCallbackList#getBroadcastItem will take care of it.
+                }
             }
+            currentUserColorListeners.finishBroadcast();
+        }
+
+        if (userAllColorListeners != null) {
+            int count = userAllColorListeners.beginBroadcast();
+            for (int i = 0; i < count; i++) {
+                try {
+                    userAllColorListeners.getBroadcastItem(i)
+                            .onWallpaperColorsChanged(wallpaperColors, which, userId);
+                } catch (RemoteException e) {
+                    // Callback is gone, it's not necessary to unregister it since
+                    // RemoteCallbackList#getBroadcastItem will take care of it.
+                }
+            }
+            userAllColorListeners.finishBroadcast();
         }
 
         if (keyguardListener != null) {
             try {
-                keyguardListener.onWallpaperColorsChanged(wallpaperColors, which);
+                keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
             } catch (RemoteException e) {
                 // Oh well it went away; no big deal
             }
@@ -595,7 +630,11 @@
     final IPackageManager mIPackageManager;
     final MyPackageMonitor mMonitor;
     final AppOpsManager mAppOpsManager;
-    final RemoteCallbackList<IWallpaperManagerCallback> mColorsChangedListeners;
+    /**
+     * Map of color listeners per user id.
+     * The key will be the id of a user or UserHandle.USER_ALL - for wildcard listeners.
+     */
+    final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> mColorsChangedListeners;
     WallpaperData mLastWallpaper;
     IWallpaperManagerCallback mKeyguardListener;
     boolean mWaitingForUnlock;
@@ -858,11 +897,6 @@
          */
         @Override
         public void onWallpaperColorsChanged(WallpaperColors primaryColors) {
-            // Do not override default color extraction if API isn't implemented.
-            if (primaryColors == null) {
-                return;
-            }
-
             int which;
             synchronized (mLock) {
                 // Do not broadcast changes on ImageWallpaper since it's handled
@@ -876,7 +910,7 @@
                 // Live wallpapers always are system wallpapers.
                 which = FLAG_SYSTEM;
                 // It's also the lock screen wallpaper when we don't have a bitmap in there
-                WallpaperData lockedWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+                WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
                 if (lockedWallpaper == null) {
                     which |= FLAG_LOCK;
                 }
@@ -906,6 +940,13 @@
                     }
                     mPaddingChanged = false;
                 }
+                try {
+                    // This will trigger onComputeColors in the wallpaper engine.
+                    // It's fine to be locked in here since the binder is oneway.
+                    mEngine.requestWallpaperColors();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to request wallpaper colors", e);
+                }
             }
         }
 
@@ -1096,7 +1137,7 @@
         mMonitor.register(context, null, UserHandle.ALL, true);
         getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs();
         loadSettingsLocked(UserHandle.USER_SYSTEM, false);
-        mColorsChangedListeners = new RemoteCallbackList<>();
+        mColorsChangedListeners = new SparseArray<>();
     }
 
     private static File getWallpaperDir(int userId) {
@@ -1252,16 +1293,24 @@
     }
 
     void switchUser(int userId, IRemoteCallback reply) {
+        WallpaperData systemWallpaper;
+        WallpaperData lockWallpaper;
         synchronized (mLock) {
             mCurrentUserId = userId;
-            WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-            // Not started watching yet, in case wallpaper data was loaded for other reasons.
-            if (wallpaper.wallpaperObserver == null) {
-                wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
-                wallpaper.wallpaperObserver.startWatching();
+            systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+            lockWallpaper = mLockWallpaperMap.get(userId);
+            if (lockWallpaper == null) {
+                lockWallpaper = systemWallpaper;
             }
-            switchWallpaper(wallpaper, reply);
+            // Not started watching yet, in case wallpaper data was loaded for other reasons.
+            if (systemWallpaper.wallpaperObserver == null) {
+                systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
+                systemWallpaper.wallpaperObserver.startWatching();
+            }
+            switchWallpaper(systemWallpaper, reply);
         }
+        notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM);
+        notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
     }
 
     void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
@@ -1634,16 +1683,30 @@
     }
 
     @Override
-    public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+    public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, "registerWallpaperColorsCallback", null);
         synchronized (mLock) {
-            mColorsChangedListeners.register(cb);
+            RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
+                    mColorsChangedListeners.get(userId);
+            if (userColorsChangedListeners == null) {
+                userColorsChangedListeners = new RemoteCallbackList<>();
+                mColorsChangedListeners.put(userId, userColorsChangedListeners);
+            }
+            userColorsChangedListeners.register(cb);
         }
     }
 
     @Override
-    public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+    public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, "unregisterWallpaperColorsCallback", null);
         synchronized (mLock) {
-            mColorsChangedListeners.unregister(cb);
+            final RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
+                    mColorsChangedListeners.get(userId);
+            if (userColorsChangedListeners != null) {
+                userColorsChangedListeners.unregister(cb);
+            }
         }
     }
 
@@ -1657,23 +1720,25 @@
     }
 
     @Override
-    public WallpaperColors getWallpaperColors(int which) throws RemoteException {
+    public WallpaperColors getWallpaperColors(int which, int userId) throws RemoteException {
         if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
             throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
         }
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, true, "getWallpaperColors", null);
 
         WallpaperData wallpaperData = null;
         boolean shouldExtract;
 
         synchronized (mLock) {
             if (which == FLAG_LOCK) {
-                wallpaperData = mLockWallpaperMap.get(mCurrentUserId);
+                wallpaperData = mLockWallpaperMap.get(userId);
             }
 
             // Try to get the system wallpaper anyway since it might
             // also be the lock screen wallpaper
             if (wallpaperData == null) {
-                wallpaperData = mWallpaperMap.get(mCurrentUserId);
+                wallpaperData = mWallpaperMap.get(userId);
             }
 
             if (wallpaperData == null) {
@@ -1872,8 +1937,11 @@
 
             try {
                 wallpaper.imageWallpaperPending = false;
+                boolean same = changingToSame(name, wallpaper);
                 if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
-                    wallpaper.primaryColors = null;
+                    if (!same) {
+                        wallpaper.primaryColors = null;
+                    }
                     wallpaper.wallpaperId = makeWallpaperIdLocked();
                     notifyCallbacksLocked(wallpaper);
                     shouldNotifyColors = true;
@@ -1888,26 +1956,31 @@
         }
     }
 
+    private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
+        if (wallpaper.connection != null) {
+            if (wallpaper.wallpaperComponent == null) {
+                if (componentName == null) {
+                    if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+                    // Still using default wallpaper.
+                    return true;
+                }
+            } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+                // Changing to same wallpaper.
+                if (DEBUG) Slog.v(TAG, "same wallpaper");
+                return true;
+            }
+        }
+        return false;
+    }
+
     boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
             boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
         if (DEBUG_LIVE) {
             Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
         }
         // Has the component changed?
-        if (!force) {
-            if (wallpaper.connection != null) {
-                if (wallpaper.wallpaperComponent == null) {
-                    if (componentName == null) {
-                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
-                        // Still using default wallpaper.
-                        return true;
-                    }
-                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
-                    // Changing to same wallpaper.
-                    if (DEBUG) Slog.v(TAG, "same wallpaper");
-                    return true;
-                }
-            }
+        if (!force && changingToSame(componentName, wallpaper)) {
+            return true;
         }
 
         try {
@@ -2234,15 +2307,35 @@
     }
 
     private void migrateFromOld() {
-        File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
-        File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
-        if (oldWallpaper.exists()) {
-            File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
-            oldWallpaper.renameTo(newWallpaper);
-        }
-        if (oldInfo.exists()) {
-            File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
-            oldInfo.renameTo(newInfo);
+        // Pre-N, what existed is the one we're now using as the display crop
+        File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
+        // In the very-long-ago, imagery lived with the settings app
+        File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
+        File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
+
+        // Migrations from earlier wallpaper image storage schemas
+        if (preNWallpaper.exists()) {
+            if (!newWallpaper.exists()) {
+                // we've got the 'wallpaper' crop file but not the nominal source image,
+                // so do the simple "just take everything" straight copy of legacy data
+                if (DEBUG) {
+                    Slog.i(TAG, "Migrating wallpaper schema");
+                }
+                FileUtils.copyFile(preNWallpaper, newWallpaper);
+            } // else we're in the usual modern case: both source & crop exist
+        } else if (originalWallpaper.exists()) {
+            // VERY old schema; make sure things exist and are in the right place
+            if (DEBUG) {
+                Slog.i(TAG, "Migrating antique wallpaper schema");
+            }
+            File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
+            if (oldInfo.exists()) {
+                File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
+                oldInfo.renameTo(newInfo);
+            }
+
+            FileUtils.copyFile(originalWallpaper, preNWallpaper);
+            originalWallpaper.renameTo(newWallpaper);
         }
     }
 
@@ -2303,12 +2396,12 @@
         JournaledFile journal = makeJournaledFile(userId);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
-        if (!file.exists()) {
-            // This should only happen one time, when upgrading from a legacy system
-            migrateFromOld();
-        }
+
         WallpaperData wallpaper = mWallpaperMap.get(userId);
         if (wallpaper == null) {
+            // Do this once per boot
+            migrateFromOld();
+
             wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
             wallpaper.allowBackup = true;
             mWallpaperMap.put(userId, wallpaper);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 78f2195..805250a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -17,7 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -539,7 +539,7 @@
                     WindowState windowState = visibleWindows.valueAt(i);
                     if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
                             || ((windowState.mAttrs.privateFlags
-                            & PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT) != 0)) {
+                            & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
                         continue;
                     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 4a04af5..e9696d2 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -611,23 +611,7 @@
         return mContainer.getTask().getConfiguration().orientation == snapshot.getOrientation();
     }
 
-    /**
-     * Remove starting window if the app is currently hidden. It is possible the starting window is
-     * part of its app exit transition animation in which case we delay hiding the app token. The
-     * method allows for removal when window manager has set the app token to hidden.
-     */
-    public void removeHiddenStartingWindow() {
-        synchronized (mWindowMap) {
-            if (!mContainer.hidden) {
-                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Starting window app still visible."
-                        + " Ignoring remove request.");
-                return;
-            }
-            removeStartingWindow();
-        }
-    }
-
-    void removeStartingWindow() {
+    public void removeStartingWindow() {
         synchronized (mWindowMap) {
             if (mContainer.startingWindow == null) {
                 if (mContainer.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c4ff455..be4b43d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -290,7 +290,7 @@
         boolean nowGone = mReportedVisibilityResults.nowGone;
 
         boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
-        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
@@ -448,7 +448,6 @@
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
                 }
                 SurfaceControl.closeTransaction();
-                removeStartingWindow();
             }
 
             if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
@@ -526,12 +525,6 @@
         return super.checkCompleteDeferredRemoval();
     }
 
-    private void removeStartingWindow() {
-        if (startingData != null && getController() != null) {
-            getController().removeStartingWindow();
-        }
-    }
-
     void onRemovedFromDisplay() {
         if (mRemovingFromDisplay) {
             return;
@@ -559,7 +552,9 @@
         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
                 + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
 
-        removeStartingWindow();
+        if (startingData != null && getController() != null) {
+            getController().removeStartingWindow();
+        }
 
         // If this window was animating, then we need to ensure that the app transition notifies
         // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
index 6b0e4c9..484987e 100644
--- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -36,11 +36,11 @@
     }
 
     @Override
-    public void onInputEvent(InputEvent event) {
+    public void onInputEvent(InputEvent event, int displayId) {
         try {
             if (event instanceof MotionEvent
                     && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-                final MotionEvent motionEvent = (MotionEvent)event;
+                final MotionEvent motionEvent = (MotionEvent) event;
                 PointerEventListener[] listeners;
                 synchronized (mListeners) {
                     if (mListenersArray == null) {
@@ -50,7 +50,7 @@
                     listeners = mListenersArray;
                 }
                 for (int i = 0; i < listeners.length; ++i) {
-                    listeners[i].onPointerEvent(motionEvent);
+                    listeners[i].onPointerEvent(motionEvent, displayId);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 233e75b..40528d0 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.power.V1_0.PowerHint;
 import android.os.Binder;
 import android.os.Debug;
@@ -243,12 +244,24 @@
                     displayId, displayInfo);
             mService.configureDisplayPolicyLocked(dc);
 
-            // TODO(multi-display): Create an input channel for each display with touch capability.
-            if (displayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
-                dc.mTapDetector = new TaskTapPointerEventListener(
-                        mService, dc);
+            // Tap Listeners are supported for:
+            // 1. All physical displays (multi-display).
+            // 2. VirtualDisplays that support virtual touch input. (Only VR for now)
+            // TODO(multi-display): Support VirtualDisplays with no virtual touch input.
+            if ((display.getType() != Display.TYPE_VIRTUAL
+                    || (display.getType() == Display.TYPE_VIRTUAL
+                        // Only VR VirtualDisplays
+                        && displayId == mService.mVr2dDisplayId))
+                    && mService.canDispatchPointerEvents()) {
+                if (DEBUG_DISPLAY) {
+                    Slog.d(TAG,
+                            "Registering PointerEventListener for DisplayId: " + displayId);
+                }
+                dc.mTapDetector = new TaskTapPointerEventListener(mService, dc);
                 mService.registerPointerEventListener(dc.mTapDetector);
-                mService.registerPointerEventListener(mService.mMousePositionTracker);
+                if (displayId == DEFAULT_DISPLAY) {
+                    mService.registerPointerEventListener(mService.mMousePositionTracker);
+                }
             }
         }
 
@@ -747,12 +760,16 @@
 
         if (mUpdateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
-            // TODO(multi-display): Update rotation for different displays separately.
-            final int displayId = defaultDisplay.getDisplayId();
-            if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) {
-                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
-            } else {
-                mUpdateRotation = false;
+
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mChildren.get(displayNdx);
+                final int displayId = displayContent.getDisplayId();
+                if (displayContent.updateRotationUnchecked(false /* inTransaction */)) {
+                    mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+                } else if (displayId == DEFAULT_DISPLAY) {
+                    // TODO(multi-display): Track rotation updates for different displays separately
+                    mUpdateRotation = false;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 0c68e2c..c58212c 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -133,7 +133,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             if (!(event instanceof MotionEvent)
                     || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
                 return;
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index dd9ba73..42a2d9d 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -24,6 +24,7 @@
 
 import com.android.server.wm.WindowManagerService.H;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
@@ -45,6 +46,13 @@
     }
 
     @Override
+    public void onPointerEvent(MotionEvent motionEvent, int displayId) {
+        if (displayId == getDisplayId()) {
+            onPointerEvent(motionEvent);
+        }
+    }
+
+    @Override
     public void onPointerEvent(MotionEvent motionEvent) {
         final int action = motionEvent.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
@@ -104,4 +112,8 @@
            mTouchExcludeRegion.set(newRegion);
         }
     }
+
+    private int getDisplayId() {
+        return mDisplayContent.getDisplayId();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5db691e..83926af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -79,6 +79,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
@@ -357,6 +358,8 @@
 
     final private KeyguardDisableHandler mKeyguardDisableHandler;
     boolean mKeyguardGoingAway;
+    // VR Vr2d Display Id.
+    int mVr2dDisplayId = INVALID_DISPLAY;
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -767,7 +770,7 @@
         }
 
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             boolean handled = false;
             try {
                 if (mDragState == null) {
@@ -7565,6 +7568,16 @@
                 accessibilityController.performComputeChangedWindowsNotLocked();
             }
         }
+
+        @Override
+        public void setVr2dDisplayId(int vr2dDisplayId) {
+            if (DEBUG_DISPLAY) {
+                Slog.d(TAG, "setVr2dDisplayId called for: " + vr2dDisplayId);
+            }
+            synchronized (WindowManagerService.this) {
+                mVr2dDisplayId = vr2dDisplayId;
+            }
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7decb11..7ec21a8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2043,7 +2043,7 @@
             super(inputChannel, mService.mH.getLooper());
         }
         @Override
-        public void onInputEvent(InputEvent event) {
+        public void onInputEvent(InputEvent event, int displayId) {
             finishInputEvent(event, true);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 23b515e..c610ca3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -20,6 +20,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -631,6 +632,10 @@
             return mSurfaceController;
         }
 
+        if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
+            windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT;
+        }
+
         w.setHasSurface(false);
 
         if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
diff --git a/services/core/jni/BroadcastRadio/Tuner.cpp b/services/core/jni/BroadcastRadio/Tuner.cpp
index f5a85c1..aa1925a 100644
--- a/services/core/jni/BroadcastRadio/Tuner.cpp
+++ b/services/core/jni/BroadcastRadio/Tuner.cpp
@@ -296,7 +296,7 @@
 
     auto selector = convert::ProgramSelectorToHal(env, jSelector);
     if (halTuner11 != nullptr) {
-        convert::ThrowIfFailed(env, halTuner11->tune_1_1(selector));
+        convert::ThrowIfFailed(env, halTuner11->tuneByProgramSelector(selector));
     } else {
         uint32_t channel, subChannel;
         if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) {
diff --git a/services/core/jni/BroadcastRadio/TunerCallback.cpp b/services/core/jni/BroadcastRadio/TunerCallback.cpp
index 04bdddf..e01d8fa 100644
--- a/services/core/jni/BroadcastRadio/TunerCallback.cpp
+++ b/services/core/jni/BroadcastRadio/TunerCallback.cpp
@@ -58,7 +58,7 @@
         jmethodID handleHwFailure;
         jmethodID onError;
         jmethodID onConfigurationChanged;
-        jmethodID onProgramInfoChanged;
+        jmethodID onCurrentProgramInfoChanged;
         jmethodID onTrafficAnnouncement;
         jmethodID onEmergencyAnnouncement;
         jmethodID onAntennaState;
@@ -111,7 +111,7 @@
     virtual Return<void> backgroundScanAvailable(bool isAvailable);
     virtual Return<void> backgroundScanComplete(ProgramListResult result);
     virtual Return<void> programListChanged();
-    virtual Return<void> programInfoChanged();
+    virtual Return<void> currentProgramInfoChanged();
 };
 
 struct TunerCallbackContext {
@@ -192,7 +192,7 @@
 
     mCallbackThread.enqueue([result, this](JNIEnv *env) {
         if (result == Result::OK) {
-            env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+            env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
         } else {
             TunerError cause = TunerError::CANCELLED;
             if (result == Result::TIMEOUT) cause = TunerError::SCAN_TIMEOUT;
@@ -254,7 +254,7 @@
     }
 
     mCallbackThread.enqueue([this, metadata](JNIEnv *env) {
-        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
     });
 
     return Return<void>();
@@ -297,11 +297,11 @@
     return Return<void>();
 }
 
-Return<void> NativeCallback::programInfoChanged() {
+Return<void> NativeCallback::currentProgramInfoChanged() {
     ALOGV("%s", __func__);
 
     mCallbackThread.enqueue([this](JNIEnv *env) {
-        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
     });
 
     return Return<void>();
@@ -379,8 +379,8 @@
     gjni.TunerCallback.onError = GetMethodIDOrDie(env, tunerCbClass, "onError", "(I)V");
     gjni.TunerCallback.onConfigurationChanged = GetMethodIDOrDie(env, tunerCbClass,
             "onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V");
-    gjni.TunerCallback.onProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
-            "onProgramInfoChanged", "()V");
+    gjni.TunerCallback.onCurrentProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
+            "onCurrentProgramInfoChanged", "()V");
     gjni.TunerCallback.onTrafficAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
             "onTrafficAnnouncement", "(Z)V");
     gjni.TunerCallback.onEmergencyAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d67bf63..a1b9099 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -287,6 +287,15 @@
                 SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
             }
 
+            //
+            // Default the timezone property to GMT if not set.
+            //
+            String timezoneProperty =  SystemProperties.get("persist.sys.timezone");
+            if (timezoneProperty == null || timezoneProperty.isEmpty()) {
+                Slog.w(TAG, "Timezone not set; setting to GMT.");
+                SystemProperties.set("persist.sys.timezone", "GMT");
+            }
+
             // If the system has "persist.sys.language" and friends set, replace them with
             // "persist.sys.locale". Note that the default locale at this point is calculated
             // using the "-Duser.locale" command line flag. That flag is usually populated by
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
index ceb92de..82ff0d8 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
+import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -61,15 +62,18 @@
 
 @RunWith(AndroidJUnit4.class)
 public class NetworkScorerAppManagerTest {
+    private static final int PACKAGE_UID = 924;
     private static String MOCK_SERVICE_LABEL = "Mock Service";
     private static String MOCK_OVERRIDEN_SERVICE_LABEL = "Mock Service Label Override";
     private static String MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
             "Mock Network Available Notification Channel Id";
+    private static final ComponentName RECO_COMPONENT = new ComponentName("package1", "class1");
 
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPm;
     @Mock private Resources mResources;
     @Mock private NetworkScorerAppManager.SettingsFacade mSettingsFacade;
+    @Mock private AppOpsManager mAppOpsManager;
     private NetworkScorerAppManager mNetworkScorerAppManager;
     private List<ResolveInfo> mAvailableServices;
 
@@ -86,45 +90,69 @@
             }
         }), eq(PackageManager.GET_META_DATA))).thenReturn(mAvailableServices);
         when(mMockContext.getResources()).thenReturn(mResources);
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+
+        mockLocationModeOn();
+        mockLocationPermissionGranted(PACKAGE_UID, RECO_COMPONENT.getPackageName());
 
         mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext, mSettingsFacade);
     }
 
     @Test
     public void testGetActiveScorer_providerAvailable() throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */);
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertEquals(MOCK_SERVICE_LABEL, activeScorer.getRecommendationServiceLabel());
     }
 
     @Test
     public void testGetActiveScorer_providerAvailable_serviceLabelOverride() throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 null /* enableUseOpenWifiPackageActivityPackage*/, true /* serviceLabelOverride */);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertEquals(MOCK_OVERRIDEN_SERVICE_LABEL, activeScorer.getRecommendationServiceLabel());
     }
 
     @Test
-    public void testGetActiveScorer_permissionMissing() throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksDenied(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */);
+    public void testGetActiveScorer_scoreNetworksPermissionMissing() throws Exception {
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksDenied(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    @Test
+    public void testGetActiveScorer_locationPermissionMissing() throws Exception {
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockLocationPermissionDenied(PACKAGE_UID, RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    @Test
+    public void testGetActiveScorer_locationModeOff() throws Exception {
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockLocationPermissionGranted(PACKAGE_UID, RECO_COMPONENT.getPackageName());
+        mockLocationModeOff();
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNull(activeScorer);
@@ -133,67 +161,63 @@
     @Test
     public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotSet()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 null /* enableUseOpenWifiPackageActivityPackage*/);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertNull(activeScorer.getEnableUseOpenWifiActivity());
     }
 
     @Test
     public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotResolved()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 "package2" /* enableUseOpenWifiPackageActivityPackage*/);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertNull(activeScorer.getEnableUseOpenWifiActivity());
     }
 
     @Test
     public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityResolved()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
         mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity());
         assertNull(activeScorer.getNetworkAvailableNotificationChannelId());
     }
 
     @Test
     public void testGetActiveScorer_providerAvailable_networkAvailableNotificationChannelIdSet() {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 null /* enableUseOpenWifiActivityPackage */, false /* serviceLabelOverride */,
                 true /* setNotificationChannelId */);
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
         assertNotNull(activeScorer);
-        assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
-        assertEquals(924, activeScorer.packageUid);
+        assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+        assertEquals(PACKAGE_UID, activeScorer.packageUid);
         assertEquals(MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID,
                 activeScorer.getNetworkAvailableNotificationChannelId());
     }
@@ -209,9 +233,8 @@
 
     @Test
     public void testGetActiveScorer_packageSettingIsInvalid() throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
-        setDefaultNetworkRecommendationPackage(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
+        setDefaultNetworkRecommendationPackage(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
         // NETWORK_RECOMMENDATIONS_PACKAGE is set to a package that isn't a recommender.
 
         final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
@@ -249,12 +272,12 @@
 
     @Test
     public void testSetActiveScorer_validPackage() throws Exception {
-        String packageName = "package";
         String newPackage = "newPackage";
-        setNetworkRecoPackageSetting(packageName);
+        int newAppUid = 621;
         final ComponentName recoComponent = new ComponentName(newPackage, "class1");
         mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+        mockRecommendationServiceAvailable(recoComponent, newAppUid, null);
+        mockLocationPermissionGranted(newAppUid, recoComponent.getPackageName());
 
         assertTrue(mNetworkScorerAppManager.setActiveScorer(newPackage));
         verify(mSettingsFacade).putString(mMockContext,
@@ -289,11 +312,9 @@
 
     @Test
     public void testUpdateState_currentPackageValid() throws Exception {
-        String packageName = "package";
-        setNetworkRecoPackageSetting(packageName);
-        final ComponentName recoComponent = new ComponentName(packageName, "class1");
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID , null);
 
         mNetworkScorerAppManager.updateState();
 
@@ -306,11 +327,13 @@
 
     @Test
     public void testUpdateState_currentPackageNotValid_validDefault() throws Exception {
-        String defaultPackage = "defaultPackage";
-        setDefaultNetworkRecommendationPackage(defaultPackage);
+        final String defaultPackage = "defaultPackage";
+        final int defaultAppUid = 621;
         final ComponentName recoComponent = new ComponentName(defaultPackage, "class1");
+        setDefaultNetworkRecommendationPackage(defaultPackage);
         mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+        mockRecommendationServiceAvailable(recoComponent, defaultAppUid, null);
+        mockLocationPermissionGranted(defaultAppUid, defaultPackage);
 
         mNetworkScorerAppManager.updateState();
 
@@ -329,8 +352,6 @@
 
         mNetworkScorerAppManager.updateState();
 
-        verify(mSettingsFacade).putString(mMockContext,
-                Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage);
         verify(mSettingsFacade).putInt(mMockContext,
                 Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
                 NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
@@ -345,8 +366,9 @@
 
         verify(mSettingsFacade, never()).putString(any(),
                 eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), anyString());
-        verify(mSettingsFacade, never()).putInt(any(),
-                eq(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), anyInt());
+        verify(mSettingsFacade).putInt(mMockContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+                NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
     }
 
     @Test
@@ -358,8 +380,9 @@
 
         verify(mSettingsFacade, never()).putString(any(),
                 eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), anyString());
-        verify(mSettingsFacade, never()).putInt(any(),
-                eq(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), anyInt());
+        verify(mSettingsFacade).putInt(mMockContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+                NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
     }
 
     @Test
@@ -370,8 +393,9 @@
 
         mNetworkScorerAppManager.updateState();
 
-        verify(mSettingsFacade, never()).putString(any(),
-                eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), any());
+        verify(mSettingsFacade).putInt(mMockContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+                NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
     }
 
     @Test
@@ -415,11 +439,10 @@
     @Test
     public void testMigrateNetworkScorerAppSettingIfNeeded_useOpenWifiSettingIsNotEmpty()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
         mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
         when(mSettingsFacade.getString(mMockContext,
@@ -441,13 +464,12 @@
     @Test
     public void testMigrateNetworkScorerAppSettingIfNeeded_useOpenWifiActivityNotAvail()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
         // The active component doesn't have an open wifi activity so the migration shouldn't
         // set USE_OPEN_WIFI_PACKAGE.
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 null /*useOpenWifiActivityPackage*/);
         when(mSettingsFacade.getString(mMockContext,
                 Settings.Global.NETWORK_SCORER_APP))
@@ -466,11 +488,10 @@
     @Test
     public void testMigrateNetworkScorerAppSettingIfNeeded_packageMismatch_activity()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
         mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
         // The older network scorer app setting doesn't match the new use open wifi activity package
@@ -492,18 +513,17 @@
     @Test
     public void testMigrateNetworkScorerAppSettingIfNeeded_packageMismatch_service()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
         mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
         // The older network scorer app setting doesn't match the active package so the migration
         // shouldn't set USE_OPEN_WIFI_PACKAGE.
         when(mSettingsFacade.getString(mMockContext,
                 Settings.Global.NETWORK_SCORER_APP))
-                .thenReturn(recoComponent.getPackageName() + ".diff");
+                .thenReturn(RECO_COMPONENT.getPackageName() + ".diff");
         when(mSettingsFacade.getString(mMockContext,
                 Settings.Global.USE_OPEN_WIFI_PACKAGE)).thenReturn(null);
 
@@ -518,11 +538,10 @@
     @Test
     public void testMigrateNetworkScorerAppSettingIfNeeded_packageMatch_activity()
             throws Exception {
-        final ComponentName recoComponent = new ComponentName("package1", "class1");
         final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
-        setNetworkRecoPackageSetting(recoComponent.getPackageName());
-        mockScoreNetworksGranted(recoComponent.getPackageName());
-        mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+        setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+        mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+        mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
                 enableUseOpenWifiComponent.getPackageName());
         mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
         // Old setting matches the new activity package, migration should happen.
@@ -566,6 +585,33 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
     }
 
+    private void mockLocationModeOn() {
+        mockLocationModeValue(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
+    }
+
+    private void mockLocationModeOff() {
+        mockLocationModeValue(Settings.Secure.LOCATION_MODE_OFF);
+    }
+
+    private void mockLocationModeValue(int returnVal) {
+        when(mSettingsFacade.getSecureInt(eq(mMockContext),
+                eq(Settings.Secure.LOCATION_MODE), anyInt())).thenReturn(returnVal);
+    }
+
+    private void mockLocationPermissionGranted(int uid, String packageName) {
+        when(mMockPm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mAppOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+    }
+
+    private void mockLocationPermissionDenied(int uid, String packageName) {
+        when(mMockPm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mAppOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+    }
+
     private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid) {
         mockRecommendationServiceAvailable(compName, packageUid, null, false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 856e940..91eb55b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -31,10 +31,13 @@
 import org.junit.runner.RunWith;
 
 import android.content.res.Configuration;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
 import android.util.SparseIntArray;
+import android.view.MotionEvent;
 
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -237,6 +240,52 @@
         assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
     }
 
+    /**
+     * Tests tapping on a stack in different display results in window gaining focus.
+     */
+    @Test
+    public void testInputEventBringsCorrectDisplayInFocus() throws Exception {
+        DisplayContent dc0 = sWm.getDefaultDisplayContentLocked();
+        // Create a second display
+        final DisplayContent dc1 = createNewDisplay();
+
+        // Add stack with activity.
+        final TaskStack stack0 = createTaskStackOnDisplay(dc0);
+        final Task task0 = createTaskInStack(stack0, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken token =
+                new WindowTestUtils.TestAppWindowToken(dc0);
+        task0.addChild(token, 0);
+        dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+        sWm.registerPointerEventListener(dc0.mTapDetector);
+        final TaskStack stack1 = createTaskStackOnDisplay(dc1);
+        final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+        final WindowTestUtils.TestAppWindowToken token1 =
+                new WindowTestUtils.TestAppWindowToken(dc0);
+        task1.addChild(token1, 0);
+        dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+        sWm.registerPointerEventListener(dc1.mTapDetector);
+
+        // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP)
+        DisplayMetrics dm0 = dc0.getDisplayMetrics();
+        dc0.mTapDetector.onPointerEvent(
+                createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true));
+        dc0.mTapDetector.onPointerEvent(
+                createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false));
+
+        // Check focus is on primary display.
+        assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow());
+
+        // Tap on secondary display
+        DisplayMetrics dm1 = dc1.getDisplayMetrics();
+        dc1.mTapDetector.onPointerEvent(
+                createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true));
+        dc1.mTapDetector.onPointerEvent(
+                createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false));
+
+        // Check focus is on secondary.
+        assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow());
+    }
+
     @Test
     @Ignore
     public void testFocusedWindowMultipleDisplays() throws Exception {
@@ -355,4 +404,18 @@
         }
         assertTrue(actualWindows.isEmpty());
     }
+
+    private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) {
+        final long downTime = SystemClock.uptimeMillis();
+        final long eventTime = SystemClock.uptimeMillis() + 100;
+        final int metaState = 0;
+
+        return MotionEvent.obtain(
+                downTime,
+                eventTime,
+                isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP,
+                x,
+                y,
+                metaState);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 828d405..95adc9c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -18,6 +18,10 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -39,6 +43,7 @@
 import android.os.RemoteException;
 import android.view.Display;
 import android.view.IWindowManager;
+import android.view.InputChannel;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
@@ -91,7 +96,14 @@
                 }).when(am).notifyKeyguardFlagsChanged(any());
             }
 
-            sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false,
+            InputManagerService ims = mock(InputManagerService.class);
+            // InputChannel is final and can't be mocked.
+            InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
+            if (input != null && input.length > 1) {
+                doReturn(input[1]).when(ims).monitorInput(anyString());
+            }
+
+            sWm = WindowManagerService.main(context, ims, true, false,
                     false, new TestWindowManagerPolicy());
         }
         return sWm;
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 177759e..db49391 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -454,10 +454,6 @@
      * @param conferenceableConnections The set of connections this connection can conference with.
      */
     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
-        if (Objects.equals(mConferenceableConnections, conferenceableConnections)) {
-            return;
-        }
-
         clearConferenceableList();
         for (Connection c : conferenceableConnections) {
             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8368f42..a78c261 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1368,17 +1368,6 @@
             "notify_international_call_on_wfc_bool";
 
     /**
-     * Determine whether user edited tether APN (type dun) has effect
-     * {@code false} - Default. APN with dun type in telephony database has no effect.
-     *
-     * {@code true}  - DUN APN added/edited in ApnEditor will be used for tethering data call.
-     *
-     * @hide
-     */
-    public static final String KEY_EDITABLE_TETHER_APN_BOOL =
-            "editable_tether_apn_bool";
-
-    /**
      * An array containing custom call forwarding number prefixes that will be blocked while the
      * device is reporting that it is roaming. By default, there are no custom call
      * forwarding prefixes and none of these numbers will be filtered. If one or more entries are
@@ -1771,7 +1760,6 @@
         sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
         sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
-        sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false);
         sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
                 null);
         sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2e7b19a..4aae1fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1654,8 +1654,7 @@
      * @hide
      */
     public String getNetworkCountryIso(int subId) {
-        int phoneId = SubscriptionManager.getPhoneId(subId);
-        return getNetworkCountryIsoForPhone(phoneId);
+        return getNetworkCountryIsoForPhone(getPhoneId(subId));
     }
 
     /**
@@ -1670,7 +1669,13 @@
      */
     /** {@hide} */
     public String getNetworkCountryIsoForPhone(int phoneId) {
-        return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) return "";
+            return telephony.getNetworkCountryIsoForPhone(phoneId);
+        } catch (RemoteException ex) {
+            return "";
+        }
     }
 
     /** Network type is unknown */
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a0e5b7b..654adb2 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -377,6 +377,13 @@
     Bundle getCellLocation(String callingPkg);
 
     /**
+     * Returns the ISO country code equivalent of the current registered
+     * operator's MCC (Mobile Country Code).
+     * @see android.telephony.TelephonyManager#getNetworkCountryIso
+     */
+    String getNetworkCountryIsoForPhone(int phoneId);
+
+    /**
      * Returns the neighboring cell information of the device.
      */
     List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index e4bf590..677585c 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -53,6 +53,7 @@
     libtinyxml2 \
     libvintf \
     libhwbinder \
+    libunwindstack \
     android.hidl.token@1.0
 
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 7e5efa1..abc0e4c 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -84,7 +84,7 @@
 
     std::string path = file->GetSource().path;
     // The name of the path has the format "<zip-file-name>@<path-to-file>".
-    path = path.substr(path.find("@") + 1);
+    path = path.substr(path.find('@') + 1);
 
     // Skip resources that are not referenced if requested.
     if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e6bf3a6..3e3f432 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -915,7 +915,7 @@
 
   bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
                      const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
-                     const Maybe<std::string> out_text_symbols_path = {}) {
+                     const Maybe<std::string>& out_text_symbols_path = {}) {
     if (!options_.generate_java_class_path) {
       return true;
     }
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 51a75d7..a9b49d9 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -520,11 +520,10 @@
   return !(*this == rhs);
 }
 
-Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok,
-                              bool end)
+Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
     : str_(s), separator_(sep), token_(tok), end_(end) {}
 
-Tokenizer::Tokenizer(StringPiece str, char sep)
+Tokenizer::Tokenizer(const StringPiece& str, char sep)
     : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
       end_(str, sep, StringPiece(str.end(), 0), true) {}
 
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index ad3989e..f89f8bf 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -240,7 +240,7 @@
    private:
     friend class Tokenizer;
 
-    iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
+    iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
 
     android::StringPiece str_;
     char separator_;
@@ -248,7 +248,7 @@
     bool end_;
   };
 
-  Tokenizer(android::StringPiece str, char sep);
+  Tokenizer(const android::StringPiece& str, char sep);
 
   iterator begin() { return begin_; }
 
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index a7fbc2e..91ca514 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -50,6 +50,7 @@
 
     int testPassCount;
     int testFailCount;
+    int unknownFailureCount; // unknown failure == "Process crashed", etc.
     bool actionsWithNoTests;
 
     Target(bool b, bool i, bool t, const string& p);
@@ -63,6 +64,7 @@
      testActionCount(0),
      testPassCount(0),
      testFailCount(0),
+     unknownFailureCount(0),
      actionsWithNoTests(false)
 {
 }
@@ -188,8 +190,13 @@
      */
     void SetCurrentAction(TestAction* action);
 
+    bool IsSuccess();
+
+    string GetErrorMessage();
+
 private:
     TestAction* m_currentAction;
+    SessionStatus m_sessionStatus;
 };
 
 void
@@ -241,8 +248,10 @@
         }
         line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName;
         print_one_line("%s", line.str().c_str());
-    } else if (resultCode == -2) {
+    } else if ((resultCode == -1) || (resultCode == -2)) {
         // test failed
+        // Note -2 means an assertion failure, and -1 means other exceptions.  We just treat them
+        // all as "failures".
         m_currentAction->failCount++;
         m_currentAction->target->testFailCount++;
         printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold,
@@ -257,9 +266,13 @@
 }
 
 void
-TestResults::OnSessionStatus(SessionStatus& /*status*/)
+TestResults::OnSessionStatus(SessionStatus& status)
 {
     //status.PrintDebugString();
+    m_sessionStatus = status;
+    if (m_currentAction && !IsSuccess()) {
+        m_currentAction->target->unknownFailureCount++;
+    }
 }
 
 void
@@ -268,6 +281,24 @@
     m_currentAction = action;
 }
 
+bool
+TestResults::IsSuccess()
+{
+    return m_sessionStatus.result_code() == -1; // Activity.RESULT_OK.
+}
+
+string
+TestResults::GetErrorMessage()
+{
+    bool found;
+    string shortMsg = get_bundle_string(m_sessionStatus.results(), &found, "shortMsg", NULL);
+    if (!found) {
+        return IsSuccess() ? "" : "Unknown failure";
+    }
+    return shortMsg;
+}
+
+
 /**
  * Prints the usage statement / help text.
  */
@@ -568,7 +599,7 @@
 /**
  * Run the build, install, and test actions.
  */
-void
+bool
 run_phases(vector<Target*> targets, const Options& options)
 {
     int err = 0;
@@ -837,6 +868,10 @@
                 printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount,
                         g_escapeEndColor, action.failCount);
             }
+            if (!testResults.IsSuccess()) {
+                printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold,
+                        testResults.GetErrorMessage().c_str(), g_escapeEndColor);
+            }
         }
     }
 
@@ -907,6 +942,7 @@
     }
 
     // Tests
+    bool hasErrors = false;
     if (testActions.size() > 0) {
         printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
         size_t maxNameLength = 0;
@@ -924,12 +960,18 @@
             Target* target = targets[i];
             if (target->testActionCount > 0) {
                 printf("   %s%s", target->name.c_str(), padding.c_str() + target->name.length());
-                if (target->actionsWithNoTests) {
+                if (target->unknownFailureCount > 0) {
+                    printf("     %sUnknown failure, see above message.%s\n",
+                            g_escapeRedBold, g_escapeEndColor);
+                    hasErrors = true;
+                } else if (target->actionsWithNoTests) {
                     printf("     %s%d passed, %d failed%s\n", g_escapeYellowBold,
                             target->testPassCount, target->testFailCount, g_escapeEndColor);
+                    hasErrors = true;
                 } else if (target->testFailCount > 0) {
                     printf("     %d passed, %s%d failed%s\n", target->testPassCount,
                             g_escapeRedBold, target->testFailCount, g_escapeEndColor);
+                    hasErrors = true;
                 } else {
                     printf("     %s%d passed%s, %d failed\n", g_escapeGreenBold,
                             target->testPassCount, g_escapeEndColor, target->testFailCount);
@@ -944,6 +986,7 @@
     }
 
     printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+    return !hasErrors;
 }
 
 /**
@@ -991,7 +1034,7 @@
         exit(0);
     } else {
         // Normal run
-        run_phases(options.targets, options);
+        exit(run_phases(options.targets, options) ? 0 : 1);
     }
 
     return 0;
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
index 0aed0ce..d921783 100644
--- a/tools/locked_region_code_injection/Android.mk
+++ b/tools/locked_region_code_injection/Android.mk
@@ -9,7 +9,7 @@
     asm-5.2 \
     asm-commons-5.2 \
     asm-tree-5.2 \
-    asm-analysis-5.2
-
+    asm-analysis-5.2 \
+    guava-20.0 \
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index 9374f23..99ef8a7 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -18,6 +18,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.commons.TryCatchBlockSorter;
@@ -32,6 +33,10 @@
 import org.objectweb.asm.tree.analysis.BasicValue;
 import org.objectweb.asm.tree.analysis.Frame;
 
+import static com.google.common.base.Preconditions.checkElementIndex;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 /**
  * This visitor does two things:
  *
@@ -140,10 +145,26 @@
                     if (operand instanceof LockTargetState) {
                         LockTargetState state = (LockTargetState) operand;
                         for (int j = 0; j < state.getTargets().size(); j++) {
+                            // The instruction after a monitor_exit should be a label for the end of the implicit
+                            // catch block that surrounds the synchronized block to call monitor_exit when an exception
+                            // occurs.
+                            checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
+                                "Expected to find label after monitor exit");
+
+                            int labelIndex = i + 1;
+                            checkElementIndex(labelIndex, instructions.size());
+
+                            LabelNode label = (LabelNode)instructions.get(labelIndex);
+
+                            checkNotNull(handlersMap.get(i));
+                            checkElementIndex(0, handlersMap.get(i).size());
+                            checkState(handlersMap.get(i).get(0).end == label,
+                                "Expected label to be the end of monitor exit's try block");
+
                             LockTarget target = state.getTargets().get(j);
                             MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
                                     target.getPostOwner(), target.getPostMethod(), "()V", false);
-                            insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+                            insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
                         }
                     }
                 }
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index 1d4f2d4..b86954d 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -228,4 +228,26 @@
         Assert.assertEquals(TestTarget.unboostCount, 1);
         Assert.assertEquals(TestTarget.invokeCount, 1);
     }
+
+    @Test
+    public void testUnboostThatThrows() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+        boolean asserted = false;
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        try {
+            t.synchronizedThrowsOnUnboost();
+        } catch (RuntimeException e) {
+            asserted = true;
+        }
+
+        Assert.assertEquals(asserted, true);
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
 }
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
index 8e7d478..d1c8f34 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -17,12 +17,17 @@
   public static int boostCount = 0;
   public static int unboostCount = 0;
   public static int invokeCount = 0;
+  public static boolean nextUnboostThrows = false;
 
   public static void boost() {
     boostCount++;
   }
 
   public static void unboost() {
+    if (nextUnboostThrows) {
+      nextUnboostThrows = false;
+      throw new RuntimeException();
+    }
     unboostCount++;
   }
 
@@ -49,4 +54,11 @@
     invoke();
     return this;
   }
+
+  public void synchronizedThrowsOnUnboost() {
+    nextUnboostThrows = true;
+    synchronized(this) {
+      invoke();
+    }
+  }
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 7972d06..a3a1054 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -94,7 +94,7 @@
 
     void reassociate();
 
-    WifiInfo getConnectionInfo();
+    WifiInfo getConnectionInfo(String callingPackage);
 
     boolean setWifiEnabled(String packageName, boolean enable);
 
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 4dc7862..a367b23 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -151,8 +151,9 @@
     /**
      * This factor is used to adjust the rate output under the new algorithm
      * such that the result is comparable to the previous algorithm.
+     * This actually converts from unit 'packets per second' to 'packets per 5 seconds'.
      */
-    private static final long OUTPUT_SCALE_FACTOR = 5000;
+    private static final long OUTPUT_SCALE_FACTOR = 5;
     private long mLastPacketCountUpdateTimeStamp;
 
     /**
@@ -198,16 +199,16 @@
                     double currentSampleWeight = 1.0 - lastSampleWeight;
 
                     txBadRate = txBadRate * lastSampleWeight
-                        + (txbad - txBad) * OUTPUT_SCALE_FACTOR / timeDelta
+                        + (txbad - txBad) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
                         * currentSampleWeight;
                     txSuccessRate = txSuccessRate * lastSampleWeight
-                        + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+                        + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
                         * currentSampleWeight;
                     rxSuccessRate = rxSuccessRate * lastSampleWeight
-                        + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+                        + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
                         * currentSampleWeight;
                     txRetriesRate = txRetriesRate * lastSampleWeight
-                        + (txretries - txRetries) * OUTPUT_SCALE_FACTOR / timeDelta
+                        + (txretries - txRetries) * OUTPUT_SCALE_FACTOR * 1000/ timeDelta
                         * currentSampleWeight;
             } else {
                 txBadRate = 0;
@@ -448,6 +449,22 @@
     }
 
     /**
+     * @hide
+     * This returns txSuccessRate in packets per second.
+     */
+    public double getTxSuccessRatePps() {
+        return txSuccessRate / OUTPUT_SCALE_FACTOR;
+    }
+
+    /**
+     * @hide
+     * This returns rxSuccessRate in packets per second.
+     */
+    public double getRxSuccessRatePps() {
+        return rxSuccessRate / OUTPUT_SCALE_FACTOR;
+    }
+
+    /**
      * Record the MAC address of the WLAN interface
      * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
      * @hide
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index c499edc..bc37810 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1643,7 +1643,7 @@
      */
     public WifiInfo getConnectionInfo() {
         try {
-            return mService.getConnectionInfo();
+            return mService.getConnectionInfo(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }